Spin a good fit for the Propeller
David Betz
Posts: 14,516
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:
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.
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 usedBefore 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, ¶ms, 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, ¶ms, 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.
Comments
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: 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: 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: or some such?
-Phil
-Phil
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: 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: 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:
As I said cognew is weird.
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.
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 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.
- 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.
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.
I think I meant something like "repeat": 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()
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
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).
I think it's quite pretty though:)
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
But now you have brought up the "@", nightmare. The less of that the better:)
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): @(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).
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.
-Phil
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
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.
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 ;-)
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.