Shop OBEX P1 Docs P2 Docs Learn Events
SPIN question - testing for a condition, and executing it at the same time — Parallax Forums

SPIN question - testing for a condition, and executing it at the same time

I'm going through tutorial examples about setting the locks. There's one example that confused me, and after thinking about it, I still don't quite understand the rules for how Spin is interpreting the line of code.

Here's the example:
if (semID := locknew) == -1
debug.Str(String("Error, no locks!"))

What this seems to do is a few things:
1. Attempt to set a lock
2. Test for the condition - Was a lock set in the pool?
3. If not, display an error.

Where I'm confused is the first line, which both sets and tests for the condition. I would have expected that you attempt to set the lock on one line, get the result (-1) back, and then evaluate the result on the next line.

The section of the manual on the IF statement gives this example, which is what I'm used to for an IF statement:
if X > 10
!outa[0]

I can't find a section of the manual that explains how the use of parenthesis in a command both allows you to execute the command and test for it at the same time. Did I overlook something?

Jeff

Comments

  • Heater.Heater. Posts: 21,230
    edited 2015-09-01 06:50
    Jeff,

    That code is exactly the same as :
    semID := locknew
    if semID == -1
         debug.Str(String("Error, no locks!"))
    
    The trick is in the parenthesis. Whatever is in the parenthesis is evaluated to some result before that result is used in further operations. In your example "semID := locknew" is in parenthesis and happens first.

    I suppose the other conceptual "trick" is that the assignment ":=" is actually an operator like "+" or "-" or anyother. When the operation is done there is a result.

    So what is the result of "semID := locknew"? Simply the new value of semID.

    With that done we now have a result to compare with -1.





  • In your example above, the expression in parentheses is evaluated first. That expression just happens to be an assignment statement, setting semID to the result of calling locknew. Then, that result (the new value of semID) is tested for equality with -1.

    -Phil
  • Heater.Heater. Posts: 21,230
    I should point out that operators have a different priority. For example in :
    a + b * c + d
    
    The multiply will be done before the additions. Which may not be what you wanted. You can add parenthesis to change that:
    (a + b) * (c + d)
    
    Now the additions happen first and the multiply last.

    This is all the same as the algebra you learned in school. With the twist that "=" in maths does not mean "assignment" and it does not produce a result.

    I'm sure there is mention of "operator precedence" in the manual.



  • I see...so this is essentially PEMDAS, with the parenthesis able to enclose an assignment as well as a value.

    Can expressions other than an assignment be used in the parenthesis?

    I went through the section in the Propeller manual on operator precedence, this specific case isn't explained like you guys did. It is covered in part in the section on LOCKNEW, etc. but not as an overall concept.

    Thanks!
  • Any expression can be put in parentheses. And anything in parentheses is executed before the result is used by operations outside the parentheses.

    -Phil
  • The semantics of Spin are so poorly documented that in many cases on cannot tell what result should be expected.

    My understanding, which may be incorrect, is that the two snippets:
    if (semID := locknew) == -1
      <statement-list>
    

    and
    semID := locknew
    if (semID == -1)
      <statement-list>
    

    are not equivalent.

    The difference is that the former, with the assignment in flow context, evaluates the result of locknew whereas the latter evaluates the value of semID. They may yield different results should other threads of execution be writing to semID coincidently with the execution of this path.

    It's also the case that they will also work differently is semID is allocated statically and of size byte or word as then semID is unsigned and the evaluation of the predicate always yields False.

    In either case, while its controversial to some, the use of locknew and (and lockret, cognew and cogret) serve no useful purpose. It's far simpler to pass lock and cog IDs into a module than to manage and/or store returned values. The allocations can always be managed statically and the errors are never recoverable.
  • Phil Pilgrim (PhiPi)Phil Pilgrim (PhiPi) Posts: 23,514
    edited 2015-09-01 18:05
    kstld wrote:
    The difference is that the former, with the assignment in flow context, evaluates the result of locknew whereas the latter evaluates the value of semID. They may yield different results should other threads of execution be writing to semID coincidently with the execution of this path.
    Except that you wouldn't do that.
    It's also the case that they will also work differently is semID is allocated statically and of size byte or word as then semID is unsigned and the evaluation of the predicate always yields False.
    Nor would you do that -- obviously.
    In either case, while its controversial to some, the use of locknew and (and lockret, cognew and cogret) serve no useful purpose.
    There is no cogret. You might want to get more Propeller experience under your belt before being so critical of it and of those who make positive contributions to the art. Perhaps then you could be among those contributors.

    -Phil
  • Heater.Heater. Posts: 21,230
    ksltd,

    You are right. There is no formal definition of Spin. Which makes writing a Spin compiler troublesome. I had to do a lot of trial and error testing just to create a PASM assembler that would understand Spin's DAT, CON, VAR block syntax. It's amazing that BST and HomeSpun did Spin compilation so well.

    However do you have any code that demonstrates that your two examples may fail to behave as expected?

    I have to dispute your claim that "the use of locknew and (and lockret, cognew and cogret) serve no useful purpose." They are the only mechanisms by which we can attain atomic operations with the Propeller and hence mutual exclusion between multiple readers and writers of a shared data area.


  • The use of lockset and lockclr are the atomic operations. Their use does not depend in any way on locknew and lockret. Locknew and lockret have no discernible value.

    I misspoke when I said cogret, I clearly meant cogstop.

    Whether or not one would "do that" is not the point. The point is that the two constructs are not equivalent in two different ways; their semantics are different. And because compilation and execution are exact sports, it's important that descriptions of compilation and execution bear some basis in fact. As I've stated previously and has been acknowledged by others here, inexact terminology and lax definitions are the bane of good results.

    Saying that obviously no one would "do that" is just bizarre. Using a byte to hold four bits of information? I do that all the time. What's not obvious is that allocating a byte won't work.

    It's a shame that critique is not seen as a positive contribution - as it's sorely needed herein. P2 development is a testament to that.

    Too, there are many using the 4x and 1x serial drivers I posted months ago. If smaller, faster, bugs fixed, features added and better documented is not a valuable contribution, despite being used by several, then I'm at a complete loss for what does constitute positive contribution?

    Moreso, it seems to me that both Phil and Heater are of the opinion that they make positive contributions but here, in this thread, their contributions are factually wrong.
    if (semID := locknew) == -1
      <statement-list>
    

    Does NOT compare the contents of semID to -1. semID is written, not read. It is the value returned by locknew that is the left-hand side of the equality operator. They both stated otherwise and they are both incorrect. The following snippet is one example of how this can be observed.
    dat
      N byte
    
    pub foo | M
    
      M := (N := -1) ' M gets the value $FFFF_FFFF
      N := -1
      M := N            ' M gets the value $0000_00FF
    

    And yet, my critique of their factually erroneous postings is, evidently, also not a positive contribution.

    I don't think Phil or Heater are bad people or otherwise ill equipped - but I do know they are wrong. And I'd like to believe that the original poster actually deserved a factually correct answer. And I'd like to believe that factually correct answers are fundamentally more positive contributions than incorrect answers. But evidently, that's just not the case.

    So tell me then, what does constitute a positive contribution?
  • Heater.Heater. Posts: 21,230
    edited 2015-09-01 20:02
    ksltd,
    The use of lockset and lockclr are the atomic operations. Their use does not depend in any way on locknew and lockret. Locknew and lockret have no discernible value.
    Really. Have you ever tried using them without a locknew?
    Moreso, it seems to me that both Phil and Heater are of the opinion that they make positive contributions but here, in this thread, their contributions are factually wrong.
    Excuse me. But you have just misquoted me or at least misinterpreted what I wrote, and then claimed that I am wrong.
    semID is written, not read. It is the value returned by locknew that is the left-hand side of the equality operator. They both stated otherwise and they are both incorrect.
    No, I did not state otherwise. Yes I was correct. Phil said the same thing in different words.

    At this point I cannot tell if you have a reading comprehension problem or purposely fishing for an argument with every post. I believe they call it "trolling" now a days.

    Either way, no one is listening any more. Not until you quit with the personal attacks and start with some semblance of a rational post.






  • ksltd wrote: »
    Locknew and lockret have no discernible value.

    Once upon a time, I thought the same thing. However, it turns out I misunderstood the way that the locks are organized. Internally, locks are actually composed of two bit fields, not one. One of the bit fields is used for locknew/lockret and the other is used for lockset/lockclr. Here's the intended usage pattern:
    1. A cog calls LOCKNEW to reserve an available lock. Internally, this is done by scanning the "reservation" bit field for the first bit that is not set. LOCKNEW sets the bit and returns the index of that bit.
    2. The cog then uses the the returned value for calling LOCKSET/LOCKCLR. In this case, LOCKSET/LOCKCLR are updating a specific offset in the "lock" bit field.
    3. Finally, when the cog no longer needs a lock, it calls LOCKRET, which "returns" the lock by clearing the bit in the "reservation" bit field.

    Now, a couple notes:
    • Of course, to be really useful, the cog that reserves a lock (via LOCKNEW) is also going to send the returned value to other cogs (e.g. during a call to COGNEW). That way, all related cogs are using the same lock index.
    • If you have complete control over the code you are running, you don't have to use LOCKNEW/LOCKRET in order to use LOCKSET/LOCKCLR. In this case, you have a priori determined the indexes to be used. Based on your comment, I suspect this is how you've been using the locks.
    • If you do not have complete control over the code, such as the use of stock objects in the OBEX, then the only safe way to ensure that your code does not use the same locks as the stock object code is to reserve a lock with LOCKNEW/LOCKRET. The assumption here, of course, is that the stock object code is also behaving properly by using LOCKNEW/LOCKRET.
    • As it turns out, if you do happen to completely control lock distribution, you can abuse the LOCKNEW/LOCKRET to act as a limited semaphore.
  • ksltd wrote: »
    The semantics of Spin are so poorly documented that in many cases on cannot tell what result should be expected.

    My understanding, which may be incorrect, is that the two snippets:
    if (semID := locknew) == -1
      <statement-list>
    

    and
    semID := locknew
    if (semID == -1)
      <statement-list>
    

    are not equivalent.

    The difference is that the former, with the assignment in flow context, evaluates the result of locknew whereas the latter evaluates the value of semID. They may yield different results should other threads of execution be writing to semID coincidently with the execution of this path.

    This is not correct. According to BST at least, the value of locknew is stored in semID but left on the stack to be later compared to -1.
  • I think his point, though, was that semID could be changed by another process between the assignment and the test in the second version, which is true. My point, though, was that you would not make semID available to another process to change to begin with, so it doesn't matter.

    -Phil
  • Heater.Heater. Posts: 21,230
    He does have a point there. When you see:
    if (semID := locknew) == -1
    The expected semantics is that semID is updated with the value of locknew. Then the comparison is done with the result of the parenthesized expression. i.e the new value of semID.

    What does the compiled code actually do?

    I have not checked but I assume it looks like:

    call locknew with result in temp
    write temp to semID
    compare temp with -1

    Where temp is the top of stack. Or perhaps a register if this were not a stack machine.

    Ah, he say, see you are wrong, it compares against the result of locknew not semID.

    So what? They are the same value! Semantically it is the same. If it is not then the compiler is broken.

    Ah, but what if another process (COG) can change semID?

    So what? If that is possible your program is broken.

    Consider: It can happen with such a broken program that locknew succeeds and you continue with the body of the "if" statement AND that some other process changes semID on you. You now have a corrupted value in semID which will cause your program to fail if it tries to use it later. I presume your program does use it later else why have it in the first place?

    So, we see that it does not matter if the compiler actually does it's comparison against semID or locknew. The semantics are the same.
  • As in the Tachyon related threads, please avoid personal attacks.

    Underlying this thread is a question of programming style driven by different needs that are only now being voiced. On the one hand, someone might have a complete program where the programmer / designer has complete control. It makes sense to allocate resources (like locks) during global initialization or as global constants and pass those resources around for use. On the other hand (the usual case for the Propeller), a program makes use of some number of objects which provide some abstraction like a UART or VGA text display and may be available in several implementations with different resource needs. Each object allocates the resources it needs, hopefully describing in its documentation what's used. A VGA text display may use one, two, or more cogs. A UART might use one or two cogs. Either might use a lock. Ideally, the calling program doesn't need to know how many cogs or locks are used. Sometimes the calling program might want to know and can ask for that information. Both programming styles are valid and have advantages and disadvantages. Spin was designed for the 2nd style with its use of some aspects of object-oriented programming. Dynamic allocation of cogs and locks is natural and used widely in the Object Exchange.
  • Heater.Heater. Posts: 21,230
    Mike,

    I don't think I made any personal attacks there. The issue about the semantics of the the code in question are there no matter who says what.

    I think you have nailed a point about the programming style there. Normally we are used to mixing and matching objects from here and there. In which case cognew/locknew make a lot of sense. Using objects from OBEX and elsewhere would be a lot more difficult without.

    Conversely if you have written all the code in your program perhaps cognew/locknew are not necessary. You know what resources you use and when.

    On the other hand, even if you do write all the code, if you have processes that are started in COGs dynamically as the program runs, depending on what inputs are coming, then you are back to needing cognew/locknew. Or you have to manage those allocations yourself.

    I guess we don't have many programs like that.
  • Mike GreenMike Green Posts: 23,101
    edited 2015-09-02 19:25
    For someone used to writing in C, declaring resources (cog #s, lock #s, etc.) in a header file that's to be included in essentially all modules makes sense. In C++, even though that supports objects (more completely than Spin), you could either allocate resources globally or dynamically in a module that's an implementation of some abstraction. In Spin, the tools don't naturally exist (they can be faked to some extent) to define globals throughout a program, so dynamic resource allocation is the natural default.

    Re: personal attacks ... nobody start!

    Re: this thread ...
    I've written a lot of Spin (and PASM) code, much of which has been posted either here or in the ObEx. I've hardly ever needed locks. I've always allocated cogs dynamically where needed (in the leaves of the object tree) using COGNEW and COGSTOP. When I've used locks, I've done the same, usually hiding the existence of the locks from the user of the object involved. That's the style I learned doing operating system and compiler development. Memory resource allocation is a bit different, particularly since Spin allocates memory statically (except for the stack). Sometimes I've implemented a simple memory allocator so I can defer assigning addresses of significant structures (large arrays, some buffers, etc.) until they're needed. It depends on the circumstances ... always tradeoffs involved.
  • Heater.Heater. Posts: 21,230
    I don't know Mike. Spin is just weird.

    On the one hand it has "objects", with methods, but they are static resources that cannot be passed around like other types. (Let's not get into that "what is a type argument")

    On the other hand it has objects like COGs and LOCKs that are dynamically allocated, like locknew and cognew, and passed around.

    Talking of C, even that has dynamic objects that can be created, used, released and destroyed. Like files, for example, that are "opened" and "closed". Rather like the Propeller's locks and cogs.
  • Yes, Spin is weird. Some of that is clearly to avoid the use of a run-time library. Some of it is probably due to Chip's previous use of Delphi Pascal ... and I like Pascal and have used it extensively. Some of the weirdness is directly related to the instruction set. With the exception of COGNEW, the COGxxx and LOCKxxx statements are directly derived from the corresponding instructions.
  • Any expression can be put in parentheses. And anything in parentheses is executed before the result is used by operations outside the parentheses.

    On a lighter note-

    Would ((Lottery Numbers)) = Tomorrows draw?

    Might be useful if it works?
  • or should that be

    ((Tomorrow's draw)) = Lottery numbers

    confused spin user
Sign In or Register to comment.