Shop OBEX P1 Docs P2 Docs Learn Events
PropGCC Native Locks Problems — Parallax Forums

PropGCC Native Locks Problems

SRLMSRLM Posts: 5,045
edited 2013-05-01 11:58 in Propeller 1
I've noticed some odd behavior with the locks, and I think it should either be changed or documented.

1. lockret does not clear a lock.

Argument for change: The intuitive use of a lock is that any time you successfully check out a lock, it is "fresh". The default behavior, though, breaks this understanding. For example:

Cog A:
int lockidA = locknew();

lockset(lockidA); // == false
//Do stuff
Cog B:
[code]//do stuff for a while
int lockidB = locknew(); //Happens to be the same as lockidA
lockset(lockidB); // == true

This behavior leads to more tightly coupled code, since your "fresh" lock depends on every usage of that particular lock before. I think it should be improved by making the lockret clear the lock before returning it.

Arguments against, and my responses:
1. (It's a performance hit to release the lock on lockret) Since lockret should be called infrequently, I don't think it will make a difference.
2. (The change deviates from standard Propeller convention) Yes, but I think it fixes a flaw in the original implementation. And, it wouldn't break current conventions. If code used lockclr before the modified lockret it would just duplicate the clear action, without breaking any programs.


2. lockset does not return true.
Argument for change: lockset does not return true, which is unintuitive (and contradicts all documentation sources).

This
	int lock = locknew();
	
	if(lockset(lock) == false){
		printf("Lock was false!\n");
	}
	if(lockset(lock) == true){
		printf("Lock was true!\n");
	}
	if(lockset(lock) != false){
		printf("Lock was not false!\n");
	}
	printf("lockset 'true' value == 0x%X\n", lockset(lock));
	
	lockclr(lock);
	lockret(lock);

Output:
Lock was false!
Lock was not false!
lockset 'true' value == 0xFFFFFFFF

As you can see, a "true" lock is not equal to true, but instead 0xFFFFFFFF. I think that this should be changed so that lockset returns C "true" or C "false".

Arguments against, and my responses:
1. (It's a performance hit to convert the output to "true" or "false") Yes, it may result in an extra instruction or two, but I think that it is worth it to make the code behave appropriately. If performance is that much of an issue, the user could use the lock instructions directly from assembly.

Comments

  • Dave HeinDave Hein Posts: 6,347
    edited 2013-04-26 12:46
    The lockxxx instructions are native instructions, so it makes sense that they act the same as they do in assembly. lockset/lockclr and locknew/lockret act independently. You don't need to use locknew prior to using lockset/lockclr. A program could hard-code the lock numbers it uses and not even bother with locknew, though it's not good practice to hard-code lock numbers or cog numbers.
  • SRLMSRLM Posts: 5,045
    edited 2013-04-26 13:45
    Dave Hein wrote: »
    The lockxxx instructions are native instructions, so it makes sense that they act the same as they do in assembly. lockset/lockclr and locknew/lockret act independently. You don't need to use locknew prior to using lockset/lockclr. A program could hard-code the lock numbers it uses and not even bother with locknew, though it's not good practice to hard-code lock numbers or cog numbers.

    I disagree that it makes sense. To me, the advantage of GCC is that you don't have to know how the hardware works, or what makes sense at the ASM level. GCC should abstract that all away.

    In regards to the independence of lockset/lockclr and locknew/lockret, I still think that returning a lock should clear it. It's the more intuitive way, and it follows with the standard pedagogy of concurrent programming.
  • Dave HeinDave Hein Posts: 6,347
    edited 2013-04-26 14:25
    I agree that the Prop's handling of locks may be confusing to a C programmer that's not familiar with the Prop. However, I wouldn't tinker with the intrinsic Prop functions. They should work the same as they do in assembly. You could define your own version of the intrinsic functions the follow the suggestions you are making. Call them SRLM_lockret, SRLM_lockset, ... Make sure you document exactly how they work so people won't confuse them with the intrinsic functions. Or you could just use the mutex lock functions, which are already well documented.
  • SRLMSRLM Posts: 5,045
    edited 2013-04-27 20:52
    Are Dave and I the only ones with opinions on this topic?
  • Heater.Heater. Posts: 21,230
    edited 2013-04-27 22:48
    SRLM,
    Are Dave and I the only ones with opinions on this topic?
    You called:)

    If it looks, like a duck, walks like a duck,....

    Following the "principle of least surprise" and give the is a Propeller we are working on then my opinion is that anything called lockset, lockclr etc should behave in the same way as the PASM counterparts.

    Anything with different semantics should have a different name so as to make it clear.
  • Phil Pilgrim (PhiPi)Phil Pilgrim (PhiPi) Posts: 23,514
    edited 2013-04-28 00:18
    If a lock is being returned to the pool of available locks via lockret, what difference does it make what state it's in? Once it's returned, you shouldn't be using it anyway.

    Anyway, it's probably better if the lock owner issues the lockret, which implies that the lock be set upon return. Otherwise, another cog may obtain the lock between the lockclr and lockret.

    -Phil
  • kuronekokuroneko Posts: 3,623
    edited 2013-04-28 00:53
    Heater. wrote: »
    Anything with different semantics should have a different name so as to make it clear.
    Seconded. Arguably the return value could simply represent the C equivalent of TRUE/FALSE as the PASM version only re/sets the carry bit. The current implementation looks more like it's been modelled after SPIN which seems odd.

    As for the returned state, that's just the way it is. If you want a defined state after fetching a new ID make sure it is what you expect/want.
  • mindrobotsmindrobots Posts: 6,506
    edited 2013-04-28 02:20
    Thirded (or dare I say FORTHED?)

    If you want Propeller locks in PropGCC, the intrinsics should work just like the PASM. If you want C compatible locks, you should use the mutex locks in pthreads.

    Of course, the option that we always have, if you want things to work the way you think they should be, then do your own thing!
  • jazzedjazzed Posts: 11,803
    edited 2013-04-29 08:47
    Requiring true to be 1 means that 'if(0x55aa) { dosomething(); }' would never dosomething(); consequently, a hoard C programmers with pitch-forks would chase you down.
  • SRLMSRLM Posts: 5,045
    edited 2013-04-29 11:19
    kuroneko wrote: »
    Seconded. Arguably the return value could simply represent the C equivalent of TRUE/FALSE as the PASM version only re/sets the carry bit. The current implementation looks more like it's been modelled after SPIN which seems odd.

    Exactly: I think lockset should return C "true" (1) or C "false", not Spin "true"(-1) and Spin "false"(0).
    If a lock is being returned to the pool of available locks via lockret, what difference does it make what state it's in? Once it's returned, you shouldn't be using it anyway.

    Anyway, it's probably better if the lock owner issues the lockret, which implies that the lock be set upon return. Otherwise, another cog may obtain the lock between the lockclr and lockret.

    -Phil

    The difference being that the next time that particular lock is checked out (perhaps by some unrelated cog or object), it is already set (which could lead to a deadlock as it waits for it to be unset).
    mindrobots wrote: »
    If you want Propeller locks in PropGCC, the intrinsics should work just like the PASM. If you want C compatible locks, you should use the mutex locks in pthreads.

    The difference that I'm arguing for is that even the Propeller locks should have C semantics.
    jazzed wrote: »
    Requiring true to be 1 means that 'if(0x55aa) { dosomething(); }' would never dosomething(); consequently, a hoard C programmers with pitch-forks would chase you down.

    "true" the constant is already 1. The difference being that your statement is really 'if(0x55aa != 0) { dosomething(); } ', and I'm saying that it should be possible to say 'if(lockset(num) == true) { dosomething; }'. At this point, that statement would never evaluate to true, which I think breaks the implied semantic contract that C programmers have.

    Re: make my own:
    Yes, I could. But I wanted to bring this point up, because I think it's a problem with the PropGCC implementation. I've taken the "make my own" route for some things that I don't agree with (ie. serial drivers), but I think that this issue falls into a different category.
  • SRLMSRLM Posts: 5,045
    edited 2013-04-29 11:19
    But ok, it looks like the consensus is to leave it the way it is. In which case, the documentation is wrong and misleading. I'm willing to edit it myself to correct it, but I don't know where: the sites.google.com HTML page, or the actual propeller.h file and regenerate with Doxygen?

    In any case, here is what needs to be changed:
    /**
     * @brief Get a new lock [b] from the pool of Propeller hardware locks.[/b] 
    [b] * @note this function follows the Spin semantics described in the Propeller Manual.[/b]
    [b] * @warning The lock my be in either the set or cleared state: a new lock is not guarenteed to be cleared.[/b]
     * @returns new lockid [b] (0 through 7) on success, -1 on failure (result is int type)[/b]
     */
    #define locknew() __builtin_propeller_locknew()
    
    /**
     * @brief Return lock to pool
    [b] * @note this function follows the Spin semantics described in the Propeller Manual.[/b]
    [b] * @warning The lock is returned in it's current state (set or cleared).[/b]
     * @param lockid [b](0 through 7, int type)[/b]
     */
    #define lockret(lockid) __builtin_propeller_lockret((lockid))
    
    /**
     * @brief Set a lock
    [b] * @note this function follows the Spin semantics described in the Propeller Manual.[/b]
     * @param lockid [b](0 through 7, int type)[/b]
     * @returns [b]int with the previous state of the lock: -1 (0xFFFFFFFF) when the previous state was set, 0 when the previous state was not set.[/b]
     */
    #define lockset(lockid) __builtin_propeller_lockset((lockid))
    
    /**
     * @brief Clear lock
    [b] * @warning This function does not precisely follow the Spin semantics described in the Propeller Manual. In particular, it does not return a value.[/b]
     * @param lockid [b](0 through 7, int type)[/b]
     */
    #define lockclr(lockid) __builtin_propeller_lockclr((lockid))
    
    
  • Dave HeinDave Hein Posts: 6,347
    edited 2013-04-29 11:34
    The include file is called "propeller.h" for a reason. It defines propeller-specific intrinsics, which by their nature should work exactly the way the propeller hardware works. I suppose a portion of the prop manual could be replicated in the comments in propeller.h. Or you could just inform the programmer that he needs to read the prop manual to fully understand the intrinsic functions.
  • Phil Pilgrim (PhiPi)Phil Pilgrim (PhiPi) Posts: 23,514
    edited 2013-04-29 11:41
    SRLM wrote:
    The difference being that the next time that particular lock is checked out (perhaps by some unrelated cog or object), it is already set (which could lead to a deadlock as it waits for it to be unset).
    Why would the routine that checks out the lock have to wait for anything? Since it presumptively owns the lock upon checkout, the lock ideally should be set until the checkout routine clears it for others to use. The thing you forget is that the Propeller's locks are cooperative. This means that everything is under program control, so there are no hardware deadlocks possible.

    -Phil
  • jazzedjazzed Posts: 11,803
    edited 2013-04-29 11:44
    >> Requiring true to be 1 ...

    That is "conceptual true" with regard to conditionals like if, while, for, etc.... Some languages will not let you use anything other than true or false or a comparison that results in true or false in if, while, for, etc.... C/C++ do not care. Whether they should or not is a pedantic debate.

    Many if not most programmers don't care what the actual value is of the bool symbol "true" because it is whatever the C++ people defined, and one should never expect to compare the true symbol (or false for that matter) to a number. A C++ bool is "true" or "false" and nothing else. Straight C does not have a bool type.
  • jazzedjazzed Posts: 11,803
    edited 2013-04-29 11:48
    It would be good to update the google site and the p2test comments (not default please). The google site uses generated doxygen but it is edited by hand, because the google site is mostly a wiki. Do you have access to the google site?

    SRLM wrote: »
    But ok, it looks like the consensus is to leave it the way it is. In which case, the documentation is wrong and misleading. I'm willing to edit it myself to correct it, but I don't know where: the sites.google.com HTML page, or the actual propeller.h file and regenerate with Doxygen?

    In any case, here is what needs to be changed:
    /**
     * @brief Get a new lock [B] from the pool of Propeller hardware locks.[/B] 
    [B] * @note this function follows the Spin semantics described in the Propeller Manual.[/B]
    [B] * @warning The lock my be in either the set or cleared state: a new lock is not guarenteed to be cleared.[/B]
     * @returns new lockid [B] (0 through 7) on success, -1 on failure (result is int type)[/B]
     */
    #define locknew() __builtin_propeller_locknew()
    
    /**
     * @brief Return lock to pool
    [B] * @note this function follows the Spin semantics described in the Propeller Manual.[/B]
    [B] * @warning The lock is returned in it's current state (set or cleared).[/B]
     * @param lockid [B](0 through 7, int type)[/B]
     */
    #define lockret(lockid) __builtin_propeller_lockret((lockid))
    
    /**
     * @brief Set a lock
    [B] * @note this function follows the Spin semantics described in the Propeller Manual.[/B]
     * @param lockid [B](0 through 7, int type)[/B]
     * @returns [B]int with the previous state of the lock: -1 (0xFFFFFFFF) when the previous state was set, 0 when the previous state was not set.[/B]
     */
    #define lockset(lockid) __builtin_propeller_lockset((lockid))
    
    /**
     * @brief Clear lock
    [B] * @warning This function does not precisely follow the Spin semantics described in the Propeller Manual. In particular, it does not return a value.[/B]
     * @param lockid [B](0 through 7, int type)[/B]
     */
    #define lockclr(lockid) __builtin_propeller_lockclr((lockid))
    
    
  • SRLMSRLM Posts: 5,045
    edited 2013-04-29 12:08
    Dave Hein wrote: »
    The include file is called "propeller.h" for a reason. It defines propeller-specific intrinsics, which by their nature should work exactly the way the propeller hardware works. I suppose a portion of the prop manual could be replicated in the comments in propeller.h. Or you could just inform the programmer that he needs to read the prop manual to fully understand the intrinsic functions.

    Ignoring a few exceptions, yes, propeller.h is a direct copy of propeller-specific functions. But the manual is "wrong" in a few cases in regards to C/C++, since "TRUE" in the manual is 0xFFFFFFFF (v1.2, page 93) and lockclr() doesn't return anything as it "should" (v1.2, page 120).
    Why would the routine that checks out the lock have to wait for anything? Since it presumptively owns the lock upon checkout, the lock ideally should be set until the checkout routine clears it for others to use. The thing you forget is that the Propeller's locks are cooperative. This means that everything is under program control, so there are no hardware deadlocks possible.

    -Phil

    In a case like this there would be deadlock:

    Psuedocode:
    Cog A:
    global int lockID = locknew();
    ...
    while(lockset(lockID) != false){} //loop while the lock is checked out.
    
    Cog B:
    while(lockID < 0 || lockID > 7){} //loop until a lock is taken from the pool.
    while(lockset(lockID) != false){} //loop while the lock is checked out.
    

    In this case, we can't use lockclr() right after the locknew() in CogA, because CogB may have checked out that lock already.

    I realize that there are other solutions to this deadlock problem, but what I'm trying to convey is that the expected behavior of a lock is to be ready to use when you get it (IMHO).
    jazzed wrote: »
    >> Requiring true to be 1 ...

    That is "conceptual true" with regard to conditionals like if, while, for, etc.... Some languages will not let you use anything other than true or false or a comparison that results in true or false in if, while, for, etc.... C/C++ do not care. Whether they should or not is a pedantic debate.

    Many if not most programmers don't care what the actual value is of the bool symbol "true" because it is whatever the C++ people defined, and one should never expect to compare the true symbol (or false for that matter) to a number. A C++ bool is "true" or "false" and nothing else. Straight C does not have a bool type.

    I'm making the argument so that we can use statements like 'while(lockset(lockID) == true){}'. It doesn't matter what the particular value of true is, as long as it compare. I don't think we are comparing to a number: I think that lockset(lockID) returns semantic T or F value.
    jazzed wrote: »
    It would be good to update the google site and the p2test comments (not default please). The google site uses generated doxygen but it is edited by hand, because the google site is mostly a wiki. Do you have access to the google site?

    Yes, I have access to both the repository and the sites pages (I've taken over maintenance of the FAQ). I'll update the p2test comments, regenerate the HTML, and see if I can cut and paste just the locks portion.
  • jazzedjazzed Posts: 11,803
    edited 2013-04-29 12:46
    SRLM wrote: »
    I'm making the argument so that we can use statements like 'while(lockset(lockID) == true){}'. It doesn't matter what the particular value of true is, as long as it compare. I don't think we are comparing to a number: I think that lockset(lockID) returns semantic T or F value.

    That is fine as long as you don't use ANSI C89 which doesn't provide a true or false symbol right?
    I guess a #ifdef could handle that though.
  • Phil Pilgrim (PhiPi)Phil Pilgrim (PhiPi) Posts: 23,514
    edited 2013-04-29 13:30
    SRLM wrote:
    In a case like this there would be deadlock:
    I'd call that a self-imposed deadlock -- one example of the many that are possible with poor programming. To me the sanest assumption is that, if you check out a lock, you should own it until you clear it for others to use.

    -Phil
  • jac_goudsmitjac_goudsmit Posts: 418
    edited 2013-04-29 19:37
    SRLM wrote: »
    Exactly: I think lockset should return C "true" (1) or C "false", not Spin "true"(-1) and Spin "false"(0).

    It's a little more subtle than that:

    In C, a Boolean test such as
    (a == b)
    
    will evaluate to 1 or 0. However, when you perform a Boolean test (e.g.
    (a && b)
    
    , the code only tests whether the values "a" and "b" are both nonzero: if one of them is zero, the expression evaluates to 0, if both of them are nonzero (regardless of their actual values), the expression evaluates to 1.

    I would say a zero/nonzero return value from a function that has a Boolean result is acceptable in C as well as C++ because if you do a simple test like
    if (function( )) ...
    
    it will behave as intended. If you have such a value (i.e. a value that can be zero or nonzero) and you want to make it into a value that's either 0 or 1, you can always prefix it with two exclamation points:
    a = !!function( ) + 999;
    

    My opinion is that PropGCC already does the minimum that's necessary to get a value that can be tested for zero or nonzero, which is perfectly fine for C as well as C++ semantics.

    I see comparison against TRUE as bad practice, at least in C. No doubt, various static analysis tools such as Lint or QAC will catch comparison against TRUE as a potential bug. When comparing a boolean value (stored in an int or unsigned int, in C), you should always just compare "the Boolean way":
    if (function( )) /* OK */
    {
       ...
    }
    
    if (!function( )) /* OK */
    {
      ...
    }
    
    if (function( ) == FALSE) /* Acceptable, but I personally don't like this style */
    {
      ...
    }
    
    if (function( ) == TRUE) /* Bad practice: could be a potential bug */
    {
       ...
    }
    
    I'm making the argument so that we can use statements like 'while(lockset(lockID) == true){}'.

    The correct code would be
    while(lockset(lockID)) { }
    
    .

    ===Jac
    (nothing personal of course, SRLM).
  • SRLMSRLM Posts: 5,045
    edited 2013-04-29 21:34
    The correct code would be
    while(lockset(lockID)) { }
    
    .

    ===Jac

    I agree that is one correct code. I personally prefer to have all boolean type expressions explicitly compared to true or false: I think it is more clear.

    And maybe that's my problem here: I'm coming at this from a C++ perspective, and not the C perspective.
    (nothing personal of course, SRLM).

    No offense taken. I just noticed a quirk, and decided to propose a change. It looks like, out of 7 people who responded, 7 like it the way it is. So, I'll pipe down :)
  • SRLMSRLM Posts: 5,045
    edited 2013-04-30 01:46
    jazzed wrote: »
    It would be good to update the google site and the p2test comments (not default please). The google site uses generated doxygen but it is edited by hand, because the google site is mostly a wiki. Do you have access to the google site?

    All done. As a side note, it would be great if PropGCC had a place where the actual .html files could be uploaded, so that two separate copies don't have to be maintained. Is there such a place?
  • TorTor Posts: 2,010
    edited 2013-04-30 02:04
    SRLM wrote: »
    Exactly: I think lockset should return C "true" (1) or C "false", not Spin "true"(-1) and Spin "false"(0).
    In C 'true' (what will evaluate to 'true' in 'if (condition)' is not 1, it's simply anything that is not 0. So in C 'false' is 0 and 'true' is !0.
    There are no built-in 'true' or 'false' keywords in C though, so maybe you're thinking of C++? I don't know if there are 'true' and 'false' keywords in C++. In any case (in C or C++) you're never supposed to compare something to true, or TRUE as you often see in C (it's common to create a #define for that). You only _assign_ to TRUE, you never compare to it. You only compare to FALSE. So 'if (A == FALSE)' is OK (as is 'if (!A)', but 'if (A == TRUE)' is never OK, you can only do 'if (A)'. A=TRUE; is what it's for.

    So in C it's equally common to see
    #define FALSE 0
    #define TRUE ˆFALSE
    
    as is
    #define FALSE 0
    #define TRUE 1
    
    or, even, #define TRUE -1
  • jazzedjazzed Posts: 11,803
    edited 2013-04-30 06:28
    SRLM wrote: »
    All done. As a side note, it would be great if PropGCC had a place where the actual .html files could be uploaded, so that two separate copies don't have to be maintained. Is there such a place?

    Thank you!

    I've struggled with .html and google sites too. I'd really like to see a "normal" web site.
  • photomankcphotomankc Posts: 943
    edited 2013-05-01 11:57
    Tor wrote: »
    You only _assign_ to TRUE, you never compare to it. You only compare to FALSE. So 'if (A == FALSE)' is OK (as is 'if (!A)', but 'if (A == TRUE)' is never OK, you can only do 'if (A)'. A=TRUE; is what it's for.

    That was what I was always taught as well. You test if the value is not 0 for a TRUE/FALSE situation. Since TRUE means all kinds of different things in different systems testing that something equals TRUE invites issues. So returning 0xFFFFFFFF is not TRUE but it is also not FALSE and if the test is written with this rule of thumb in mind then it evaluates correctly. I do think updating the documentation is a fine idea so that is clear.
  • photomankcphotomankc Posts: 943
    edited 2013-05-01 11:58
    Tor wrote: »
    You only _assign_ to TRUE, you never compare to it. You only compare to FALSE. So 'if (A == FALSE)' is OK (as is 'if (!A)', but 'if (A == TRUE)' is never OK, you can only do 'if (A)'. A=TRUE; is what it's for.

    That was what I was always taught as well. You test if the value is not 0 for a TRUE/FALSE situation. Since TRUE means all kinds of different things in different systems testing that something equals TRUE invites issues. So returning 0xFFFFFFFF is not TRUE but it is also not FALSE and if the test is written with this rule of thumb in mind then it evaluates correctly. I do think updating the documentation is a fine idea so that is clear.
Sign In or Register to comment.