Shop OBEX P1 Docs P2 Docs Learn Events
Spin a good fit for the Propeller — Parallax Forums

Spin a good fit for the Propeller

David BetzDavid Betz Posts: 14,516
edited 2014-09-02 10:49 in Propeller 1
I've said a number of times that I didn't see anything in Spin that particularlly made it suitable for the Propeller other than its built-in functions like cognew, coginit, etc. Since those functions are shared by PropGCC and probably Catalina as well, it seemed that C or C++ could be used just as effectively as Spin for most purposes. While I still believe this is mostly true I've recently spent a fair amount of time debugging a problem that shows that there is a real advantage to the Spin semantics over C semantics for launching code in other COGs. Of course, its support for multi-core programs is the Propeller's greatest strength so this is no trivial matter. Here is the problem I ran into. I've been working on converting JonnyMac's DEFCON badge code to C++ and one of the functions that I had to convert is this one:
pri start_animation(p_table, cycles)

'' Start animation in background cog
'' -- allows LED animation while doing other processes
'' -- p_table is pointer (address of) animation table
'' -- set cycles to 0 to run without stopping

  stop_animation
  
  anicog := cognew(run_animation(p_table, cycles), @anistack) + 1  

  return anicog                                                 ' return cog used
Before I saw this code I wasn't aware that you could express the first argument to cognew as a Spin function call complete with arguments. I ended up translating this code to C++ in the following way:
typedef struct {
  uint8_t *volatile p_table;
  int cycles;
} aniparams;

int start_animation(uint8_t *p_table, int cycles)
{
  aniparams params;
  
// Start animation in background cog
// -- allows LED animation while doing other processes
// -- p_table is pointer (address of) animation table
// -- set cycles to 0 to run without stopping

  stop_animation();
  
  params.p_table = p_table;
  params.cycles = cycles;
  anicog = cogstart(run_animation, &params, anistack, sizeof(anistack));
  
   return anicog;                                                // return cog used
}
This introduced a bug that it took me some time to resolve. I guess what must be happening in the Spin case is that the invocation of cognew creates a stack frame on the new COG's stack containing the function arguments. This means that those arguments are safe for the new COG to access even after the start_animation function returns. In my C++ code, I created a stack variable containing those parameters since C has no way of supporting the function-call-with-parameters syntax supported by Spin. This caused a subtle bug if the run_animation function accesses those arguments after the start_animation function returns. I ended up "fixing" this with the following more complex code:
int start_animation(uint8_t *p_table, int cycles)
{
  aniparams params;
  
// Start animation in background cog
// -- allows LED animation while doing other processes
// -- p_table is pointer (address of) animation table
// -- set cycles to 0 to run without stopping

  stop_animation();
  
  params.p_table = p_table;
  params.cycles = cycles;
  anicog = cogstart(run_animation, &params, anistack, sizeof(anistack));
  
  // wait for the animiation cog to initialize
  if (anicog >= 0)
    while (params.p_table)
      ; 

  return anicog;                                                // return cog used
}

In this case, the run_animation function fetches its parameters from the structure passed in the par register and then sets the params.p_table field to 0 to indicate that it has completed initialization. Only then can the start_animation function safely return because the COG function no longer needs access to the params structure. This results in more complex code than is required by Spin and worse, you have to remember to do this or you introduce subtle concurrency problems in the translated code.

So, I was wrong. Spin does have language features that better support multi-core programming. It isn't just the library that makes it suited to the Propeller.
«1

Comments

  • potatoheadpotatohead Posts: 10,261
    edited 2014-08-31 12:19
    This is a great post, and I'm glad you got to the core trouble. Good info for any of us using C.
  • Heater.Heater. Posts: 21,230
    edited 2014-08-31 12:31
    Interesting observation. I have seen that done a few times but never used it myself. Never thought about it much.

    Just now I can't think of any language that has a construct like that.

    It's pretty weird though isn't it?

    I mean, in general we can write:
            x := f1(f2(p1, p2),  y) + 1  
    
    Normally we would expect the semantics of that to be:
    a) Evaluate p1 and p2
    b) Evaluate f2 of p1 and p2 -- causes a function call
    c) Evaluate f1 of a) and y -- causes a function call
    d) Add one and assign it to x.

    but in the special case that f1 = "cognew" that normal semantics is ignored and it does something completely different:
            anicog := cognew(f1(p1, p2), y) + 1  
    
    Clearly here cognew returns before f1 ever completes.
    The result of cognew has nothing to do with f1, p1 or p2 !

    Some language designers might call this inconsistent and surprising behavior "broken".

    In this case it looks as if "cognew" is acting more like an operator on a function rather than a normal function call itself.

    Why is the syntax not:
            x := cognew f1(p1, p2, y)
    
    or some such?
  • David BetzDavid Betz Posts: 14,516
    edited 2014-08-31 12:35
    I think you're right. Cognew isn't really a function nor probably is coginit. It is actually Spin language syntax that happens to look like a function call.
  • Phil Pilgrim (PhiPi)Phil Pilgrim (PhiPi) Posts: 23,514
    edited 2014-08-31 12:53
    Typically, you'd want a pointer to a function for cognew. In Spin, a "pointer" is a symbol with an "@" prefix. But that syntax is reserved for cognews to PASM. So the reason it's done the way it is with Spin cogs is simply to disambiguate between Spin cogs and PASM cogs. Well, also, it's because Spin doesn't recognize pointers to functions anyway, so why make a special case for cognew?

    -Phil
  • David BetzDavid Betz Posts: 14,516
    edited 2014-08-31 19:59
    Typically, you'd want a pointer to a function for cognew. In Spin, a "pointer" is a symbol with an "@" prefix. But that syntax is reserved for cognews to PASM. So the reason it's done the way it is with Spin cogs is simply to disambiguate between Spin cogs and PASM cogs. Well, also, it's because Spin doesn't recognize pointers to functions anyway, so why make a special case for cognew?

    -Phil
    But it's more than just treating the function name as a pointer to the function. It's also arranging to pass the parameters as if they were through a normal function call.
  • Phil Pilgrim (PhiPi)Phil Pilgrim (PhiPi) Posts: 23,514
    edited 2014-08-31 23:16
    David Betz wrote:
    But it's more than just treating the function name as a pointer to the function. It's also arranging to pass the parameters as if they were through a normal function call.
    True enough. It's more like a command line process invocation that includes parameters. If Spin had something like Perl's eval, it might be appropriate to put the function call in quotes and pas it as a string. But it is what it is, it's unambiguous, and it works, despite the funky syntax. So I guess, in the end, the oddness doesn't really matter.

    -Phil
  • Heater.Heater. Posts: 21,230
    edited 2014-08-31 23:24
    Quite so David. It's not just a function pointer.

    What it is doing is taking the function to be called and the parameters you want to call that function with and passing them to some other function "cognew" to actioned in another place and an other time.

    I think this is what they would call a "closure" in other languages.

    cognew is odd because it is not a regular method. You cannot write a cognew in Spin itself.

    There are similar things in other languages. For example in JavaScript there is a setTimeout system function that takes a function and runs it at some time in the future:
    interval = setTimeout (someFuntion, someTimeout);
    
    Like cognew setTimeout cannot be written in JavaScript itself it has to be provided by the run time.

    If you want to pass parameters to someFuntion there you cannot do it as easily as in Spin's cognew, you need to create a closure by wrapping it in a function:
    interval = setTimeout (function () { someFuntion (x, y) }, someTimeout);
    
    Here, the anonymous function is called at some time in the future which in turn calls someFunction with the parameters we gave it when the setTimeout was called.

    One could imagine some form of parallel JavaScript that started processes on new processors with something like:
        anicog = cognew(function() { run_animation(table, cycles); }, anistack) + 1;
    

    As I said cognew is weird.
  • ElectrodudeElectrodude Posts: 1,658
    edited 2014-09-01 11:02
    Cognew isn't really a function; it just has the same syntax as one. string(), constant(), and float() aren't functions either but they look like them.

    Can anyone think of a better syntax for this? I can't. Anonymous functions aren't an option because they don't exist in spin.

    @Heater:
    Yes, it's called a closure when you define a function inside another function that references the enclosing function's local variables.
  • Heater.Heater. Posts: 21,230
    edited 2014-09-01 11:23
    Quite so. It's not a closure exactly. But it does capture those parameters in a sort of closure like way.

    I have yet to think of a better syntax for cognew. It's a mess because it mixes up silly hardware details with our high level program logic. Like the cog id and where the stack is.

    We could treat it as a language construct like "repeat" or "if" and write something like
        cognew someMethod(x, y, z) stack
    
    But that does not work because we need to get the cog id returned back. The cog id of course normally has nothing to do with the program we are trying to write, no more than a RAM address of a variable we use. The stack thing is a low level detail.

    I don't know.
  • pik33pik33 Posts: 2,366
    edited 2014-09-01 11:48
    Cognew is a function. It:

    - starts a new cog
    - returns a started cog id.

    It is of course a strange function because its result doesn't depend on its arguments It is something like a operating system function call.
    anicog := cognew(f1(p1, p2), y) + 1
    
    'cannot be replaced with
            
    x := cognew f1(p1, p2, y) 
    
    
    

    No!

    f1 is a function which will run in its own cog (=new thread)
    p1, p2 are parameters of f1

    but y is the parameter of cognew! It is the pointer for new thread's stack Syntax like x:= cognew f1(p1,p2,y) looks like y is the parameter of f1, but it is not.
  • Heater.Heater. Posts: 21,230
    edited 2014-09-01 12:00
    Sorry, yes you are right.

    I think I meant something like "repeat":
    cognew f1(p1, p2) stack
    But then what about the returned cog id?

    I can't think of a clean way to do it.

    Perhaps language purity meets pragmatic necessity in cognew()
  • David BetzDavid Betz Posts: 14,516
    edited 2014-09-01 12:25
    The current cognew syntax is fine. It just surprised me when I saw the parameter list because I didn't realize it worked that way. It's actually very handy to be able to handle parameters to the COG function so easily.
  • Heater.Heater. Posts: 21,230
    edited 2014-09-01 12:32
    David,
    The current cognew syntax is fine. It just surprised me...
    That is really the point of debate.

    It's better if a language syntax and semantics does not have surprises.

    A thing that looks like a method call should be a method call. Those funny exceptions cause confusion. As has happened to you when translating the code to C++.

    We have yet to think of a better way to express syntactically what cognew does. But like the bazillion exceptions in the English language perhaps we just have to live with such things in all programming languages.
  • Phil Pilgrim (PhiPi)Phil Pilgrim (PhiPi) Posts: 23,514
    edited 2014-09-01 12:35
    David Betz wrote:
    The current cognew syntax is fine.
    I agree. As long as it's completely unambiguous, it doesn't have to be pretty.

    -Phil
  • David BetzDavid Betz Posts: 14,516
    edited 2014-09-01 12:37
    Heater. wrote: »
    David,

    That is really the point of debate.

    It's better if a language syntax and semantics does not have surprises.

    A thing that looks like a method call should be a method call. Those funny exceptions cause confusion. As has happened to you when translating the code to C++.

    We have yet to think of a better way to express syntactically what cognew does. But like the bazillion exceptions in the English language perhaps we just have to live with such things in all programming languages.
    Actually, I could guess right away what was happening. I just failed to take into account what that meant for my C++ translation. In particular, I failed to recognize that the way I handle arguments in C++ resulted in a race condition that was not present in the original Spin code. It wasn't that I didn't understand the construct, I just failed to notice some subtlties that turned out to matter in this case.
  • ElectrodudeElectrodude Posts: 1,658
    edited 2014-09-01 12:39
    I wasn't saying I didn't like the cognew syntax. I just said there isn't any other way and that spin abuses function call notation for other things as well such as string, constant, and float (and trunc). Also, lookdown and such aren't strictly functions because of the colon syntax.

    Spin also has other weird syntax things like ina vs. ina[index] - one thinks ina is a long and the other thinks it's a bit array. They may look strange at first, but I like them. There's no syntax like this in C, meaning you are forced to either do the bit twiddling yourself or to call functions and face function call overhead. It's part of what makes Spin the best language for the propeller (second to PASM, of course).
  • Heater.Heater. Posts: 21,230
    edited 2014-09-01 12:39
    Given the reason for this thread in the first place it seems that the current syntax is ambiguous.

    I think it's quite pretty though:)
  • Phil Pilgrim (PhiPi)Phil Pilgrim (PhiPi) Posts: 23,514
    edited 2014-09-01 12:46
    I think Chip did the right thing. Imagine if something like this were possible:
    p := @some_proc(x, y)
    ...
    cognew(p, @stack)
    

    What if p were not really a reference to a procedure call? And when do x and y get evaluated? When p is assigned, or when some_proc gets called? By choosing the syntax that he did, he forced everything to be resolvable at compile time, thus eliminating a whole host of potential errors.

    -Phil
  • Heater.Heater. Posts: 21,230
    edited 2014-09-01 12:52
    I agree Phil.

    But now you have brought up the "@", nightmare. The less of that the better:)
  • ElectrodudeElectrodude Posts: 1,658
    edited 2014-09-01 13:05
    I think Chip did the right thing. Imagine if something like this were possible:
    p := @some_proc(x, y)
    ...
    cognew(p, @stack)
    

    What if p were not really a reference to a procedure call? And when do x and y get evaluated? When p is assigned, or when some_proc gets called? By choosing the syntax that he did, he forced everything to be resolvable at compile time, thus eliminating a whole host of potential errors.

    -Phil

    Exactly. But when you do the p:=@..., you would have to specify the stack pointer there, so the parameters could be put somewhere. That would be even uglier. Chip's cognew syntax is the only way.

    Also, how would you differentiate your pointer spin cognew from a PASM cognew?

    This might make more sense than your example (and will work in the spin compiler I'm writing along with the standard syntax):
    p:=@some_proc
    cognew(@(p)(x,y), @stack)
    
    @(func)(params, ...) would call a function by reference. Got a better syntax? It doesn't have to be @.
    p is a pointer to a method descriptor (i.e. the things found in the method table, not what they point to).

    And yes, my spin compiler will have an option (only optional for compatibility) to fix the @ operator to spit out final addresses instead of ones relative to pbase.

    I think everyone is misunderstanding me and thinks I don't like cognew's syntax. I like its syntax and, while it seems a bit odd at first, it's the best way and makes sense after you think about it (and if you look at how the spin interpreter works internally).
  • Heater.Heater. Posts: 21,230
    edited 2014-09-01 13:31
    You should not change the semantics of the @ operator in Spin.

    Other existing compilers have already tackled the problem of getting real addresses by introducing "@@@".

    Ugly as hell but it's the kind of mess you get into when merging two languages together. In this case Spin and PASM.
  • David BetzDavid Betz Posts: 14,516
    edited 2014-09-01 15:05
    Heater. wrote: »
    Given the reason for this thread in the first place it seems that the current syntax is ambiguous.

    I think it's quite pretty though:)
    Yes, the current syntax is ambiguous but I didn't have any trouble guessing which interpretation was correct. As I've said before, my problems more stemmed from not translating the semantics correctly.
  • Phil Pilgrim (PhiPi)Phil Pilgrim (PhiPi) Posts: 23,514
    edited 2014-09-01 15:08
    The current syntax is not ambiguous in any formal sense. The semantics that derive from it are completely well-defined and can never lead to more than one interpretation.

    -Phil
  • David BetzDavid Betz Posts: 14,516
    edited 2014-09-01 15:20
    The current syntax is not ambiguous in any formal sense. The semantics that derive from it are completely well-defined and can never lead to more than one interpretation.

    -Phil
    It could be considered ambiguous if you don't realize that cognew is not a function. In that case, you would expect that (going back to my original example) "run_animation(p_table, cycles)" would be evaluated and that the result it returns would be passed as the first argument to cognew. Or, "run_animation(p_table, cycles)" could be interpreted as an expression to be evaluated in the context of a newly started COG (which is, of course, the correct interpretation). In any case, I can't think of any better syntax off the top of my head and what we have now doesn't seem to confuse anyone except me! :-)
  • ElectrodudeElectrodude Posts: 1,658
    edited 2014-09-01 15:38
    Heater. wrote: »
    You should not change the semantics of the @ operator in Spin.

    Other existing compilers have already tackled the problem of getting real addresses by introducing "@@@".

    It will be optionally enabled per file in a C #pragma-ish way, so normal objects will still work. And I'll also implement @@@. I have no intentions of breaking backwards compatibility for other objects, and anything that might break backwards compatibility will have to be enabled separately in every single file that wants to use the non-standard backwards-compatibility-breaking feature.
  • Phil Pilgrim (PhiPi)Phil Pilgrim (PhiPi) Posts: 23,514
    edited 2014-09-01 17:38
    David Betz wrote:
    It could be considered ambiguous if you don't realize that cognew is not a function.
    No it could not. Being uninformed does not qualify one to claim ambiguity. Ambiguilty has a formal definition in computer linguistics, wherein a string can be parsed in more than one way with a given grammar, leading to different semantics. Such is not the case with Spin.

    -Phil
  • David BetzDavid Betz Posts: 14,516
    edited 2014-09-01 17:53
    No it could not. Being uninformed does not qualify one to claim ambiguity. Ambiguilty has a formal definition in computer linguistics, wherein a string can be parsed in more than one way with a given grammar, leading to different semantics. Such is not the case with Spin.

    -Phil
    Yeah, I guess that's true. All I really meant is that there is a seemingly obvious interpretation of what that expression ought to mean but it doesn't actually mean that once you know the true semantics of the language. I didn't mean it was ambiguous in any formal sense.
  • Heater.Heater. Posts: 21,230
    edited 2014-09-01 21:00
    Presumably it's not ambiguous in the formal sense that Phil mentions. After all the various Spin compilers do actually manage to compile it correctly.

    My feeling is that it violates the principle of least surprise. It's not just you David, I also had to look twice when I first saw it.

    However it's only a small violation. It looks nice. It works. And we can't think of a better syntax.

    Compared to the surprises you find in C and other languages we should not complain.
  • ErNaErNa Posts: 1,752
    edited 2014-09-01 23:24
    Hi, I try to follow this discussion, but as I use language more process-oriented: (I just speak as used to, don't care for grammar and syntax). there can be ambiguity. ;-)
    My feeling is, the issue here comes from using a sequential language in a parallel environment. We call functions, but start processes! A function call guarantees availability of a result the moment the program, interrupted by the function, resumes. Ouups: interrupt? Isn't that something different? No. The only difference to "normal" interrupts is the mechanism to trigger the routine. The next propeller language should be occam ;-)
  • Heater.Heater. Posts: 21,230
    edited 2014-09-01 23:44
    ErNa,
    We call functions, but start processes! ... A function call guarantees availability of a result the moment the program ... resumes.
    I see what you mean.

    However here we call "cognew". Cognew is a function and it performs the function of starting a COG. It returns the result of trying to do that. It's not a Spin method but it looks like a method call with parameters and a result and it does return when completed.

    No problem there.

    Perhaps the problem is at the other end. The "process" we set in motion with COG new is not annotated anywhere in the Spin language. It's just another method on an object. Looks like any other method we may call directly. Which we can do as well. It can have a return vale, what happens to that by the way?

    Unlike OCCAM and it's modern incarnation, XC, there is no definition of "parallel process" in the Spin language. It's just cobbled together in that cognew function. Strange really given that Spin was designed hand in hand with the multi-core chip it was to run on.

    OCCAM, or better yet XC would be great on the P2 or P3 one day. It's going to need some hardware added for those "channels" and event driven programming used by such languages.
Sign In or Register to comment.