Shop OBEX P1 Docs P2 Docs Learn Events
Are all assignments in SPIN atomic? — Parallax Forums

Are all assignments in SPIN atomic?

agsags Posts: 386
edited 2013-05-22 20:53 in Propeller 1
I'm battling a bug that looks like cog contention. I'm turning over every rock for the third time.

Is it safe to say that all assignments in SPIN are atomic operations? By this, I mean that there are no intermediate steps between the initial value and ending value (looking for "half-complete" operations being intercepted by other cogs running, causing problems). For instance:
value := op1 << 4 | op2 & $FF

Are all the intermediate steps stored in a scratch area on the stack, and when completed, the final answer is assigned to "value" (this is what I mean by "atomic") or is value somehow involved in the steps along the way, changing value based on intermediate calculations before the final assignment?

Another example would be:
value |= op1 | op2

This could be done (among many methods that produce the same result) as:

temp := op1 | op2
temp := temp | value
value := temp

or

value := value | op1
value := value | op2

The first undergoes intermediate steps before the final result is stored in "value"; the second does not. Is there any guarantee that all assignments will only modify the target exactly once?

Comments

  • Heater.Heater. Posts: 21,230
    edited 2013-05-07 21:59
    A very interesting question.

    My guess would be that the "value" is only written once. But life is full of surprises....

    If you compile that code with BST it has an option to create a listing file of all the Spin byte codes it generates. You could look in there and see what it actually does.

    Failing that you could of course introduce your own temp variable and make sure the final assignment is atomic.
    valueTemp := op1 << 4 | op2 & $FF
    value := valueTemp
    
    If that fixes your code it's a good clue that their was a race going on.

    Then again you could arrange for a second cog to read "value", perhaps quickly in PASM, and have it check for unexpected values.
  • agsags Posts: 386
    edited 2013-05-07 22:20
    Good suggestion - unfortunately I don't use BST, and am not a SPIN expert (in reading the byte code). I'd have to try a lot of different scenarios to feel comfortable making a statement that all assignments are atomic. I was wondering if it was among the undocumented principals of SPIN that this was guaranteed. I'm afraid that this pattern is spread throughout my code and will take some time to find and change (if that is the problem). I'll try making the changes and see what happens.
  • Mike GreenMike Green Posts: 23,101
    edited 2013-05-07 22:41
    Assignments are only atomic in terms of multi-byte values. The right side of an assignment is computed on the stack and other cogs can affect any of the variables used in that expression or a subscript used on the left side of the assignment ... at any time except in the middle of a fetch of a multi-byte value (word or long) which is atomic. The last operation is the storage of that stack value into a variable (or special purpose register like OUTA). That storage is atomic in the sense that, when the value is stored, all bytes of a word or long are updated in a single hub memory operation that is atomic. No other cog has access to hub memory until that operation is complete.

    All fetches of values from hub memory are done with the RDBYTE, RDWORD, and RDLONG instructions which are atomic. All stores of values to hub memory are done with the WRBYTE, WRWORD, and WRLONG instructions which are also atomic. Special purpose registers are unique to each cog and can't be accessed from other cogs.
  • agsags Posts: 386
    edited 2013-05-07 23:12
    Mike Green wrote: »
    The right side of an assignment is computed on the stack...

    Thanks Mike. That is what I was looking for. It's good and bad: good because it is a good "contract" to have, bad because that means I still haven't found the bug...

    Specifically, what I often do is build a command to pass (through PAR) to a cog. That can look like:
    command := operator << 16 | @params
    repeat while command
    

    My concern was that if the cog happened to loop around and see the value of "command" in an intermediate state, that would cause problems.
  • Mike GreenMike Green Posts: 23,101
    edited 2013-05-08 05:59
    What you describe is a common method for assembling a packet to send to a cog and waiting for the cog to finish the operation requested.
  • agsags Posts: 386
    edited 2013-05-08 08:39
    Not to hijack my own thread but...

    It only recently occurred to me that sometimes the fastest (and most compact) way to get data/parameters from SPIN/hub to PASM/cog is not packing information into the least number of longs possible. Granted, reading a long in 7-22 clocks is the fastest thing to do, but when I stopped to think about how many clocks are consumed in SPIN doing shifts, or/and, etc, I've realized that it may be faster, and take less space, to pay the price of pulling multiple partially-utilized longs from hub to cog.

    Of course, I don't use BST and can't see the compiled SPIN byte codes (and even if I could it might not help me) so this is just intuition, not based on concrete studies of specific cases.
  • kwinnkwinn Posts: 8,697
    edited 2013-05-08 09:58
    ags wrote: »
    Not to hijack my own thread but...

    It only recently occurred to me that sometimes the fastest (and most compact) way to get data/parameters from SPIN/hub to PASM/cog is not packing information into the least number of longs possible. Granted, reading a long in 7-22 clocks is the fastest thing to do, but when I stopped to think about how many clocks are consumed in SPIN doing shifts, or/and, etc, I've realized that it may be faster, and take less space, to pay the price of pulling multiple partially-utilized longs from hub to cog.

    Of course, I don't use BST and can't see the compiled SPIN byte codes (and even if I could it might not help me) so this is just intuition, not based on concrete studies of specific cases.

    Yep, getting the fastest overall execution out of interacting spin and pasm programs is not as simple as it appears at first glance. Optimizing how they interact can speed execution times by as much as 400% or more.
  • Heater.Heater. Posts: 21,230
    edited 2013-05-08 19:39
    Yep, all this business of packing commands and parameters and addresses into a single long parameter is nonsense.
    The first order of the day should be to get the thing working at all. The code should be simple, clear and obvious.
    After that you might want to think about optimizing for speed or space.

    In this case, for example, you have been fretting over whether some operator and some parameter arrive in a long atomically. Had you written it using two longs so that you set up the parameter(s) and then set the operator/command this would never have been an issue allowing your mind to time to find the real bug wherever it is.
  • agsags Posts: 386
    edited 2013-05-22 20:53
    Just to bring this to a close:

    1) Yes, as I said, I now realize that packing parameters into the minimal number of longs for SPIN/PASM interaction is not necessarily increasing performance. As a matter of fact, it is more likely to be slowing things down. (Side note: I'm now adopting the practice that if the parameters I want to pass to PASM code are already ordered longs on the stack (i.e. they are arguments to the SPIN method that will pass them to a PASM cog) passing the address of the first parameter (on the stack) to the PASM cog (in the par register) is probably the fastest way to get things done - no manipulation/packing in SPIN at all)

    2) I agree that it compounds the effort with added complexity to prototype, test and debug code that has (potentially) already been obscured by premature optimizations.
Sign In or Register to comment.