propeller2.h for C compilers
![ersmith](https://forums.parallax.com/uploads/userpics/844/n9D3ZZGYBZH03.jpg)
in Propeller 2
Now that there are several C compilers working for P2, it seems like we should try to get consensus on some things. It'd be nice if code could be easily ported between compilers. It'll be very confusing and frustrating for users if there isn't at least a baseline commonality between C compilers on the platform (naturally each compiler will have its own particular features and advantages, but lots of code *should* be portable between them!)
It seems that we'll want a few common things at a minimum:
(1) A macro that identifies the platform as a Propeller 2. I've been using -D__P2__ in fastspin, but this may be too concise. Other reasonable options are -D__prop2__ or -D__propeller2__.
(2) A header file defining intrinsics that everyone can use. I've suggested <propeller2.h> as the name, but I'm open to variations on this.
(3) A commonly agreed upon set of intrinsics for things like pin manipulation, COG start/stop, timers, CORDIC access, and ??? (maybe the streamer).
One obvious choice for names (and functions) to implement is to more or less follow what Spin2 does. @cgracey , do you have a preliminary list of the names for the Spin2 functions for these things? E.g. is there a Spin2 function that does what the "drvh" instruction does? We could adopt the same system for C, perhaps with an underscore prepended (to keep the functions from conflicting with user names -- the C standard says that global function names starting with an underscore are reserved for the system).
If there isn't already a Spin2 standard, we could just use the instruction names, e.g.:
@RossH , @"Dave Hein" , does this sound reasonable for Catalina and p2gcc respectively? If we three can agree on a naming scheme we can have it implemented in Catalina, p2gcc, fastspin, and the Risc-V emulator.
Regards,
Eric
It seems that we'll want a few common things at a minimum:
(1) A macro that identifies the platform as a Propeller 2. I've been using -D__P2__ in fastspin, but this may be too concise. Other reasonable options are -D__prop2__ or -D__propeller2__.
(2) A header file defining intrinsics that everyone can use. I've suggested <propeller2.h> as the name, but I'm open to variations on this.
(3) A commonly agreed upon set of intrinsics for things like pin manipulation, COG start/stop, timers, CORDIC access, and ??? (maybe the streamer).
One obvious choice for names (and functions) to implement is to more or less follow what Spin2 does. @cgracey , do you have a preliminary list of the names for the Spin2 functions for these things? E.g. is there a Spin2 function that does what the "drvh" instruction does? We could adopt the same system for C, perhaps with an underscore prepended (to keep the functions from conflicting with user names -- the C standard says that global function names starting with an underscore are reserved for the system).
If there isn't already a Spin2 standard, we could just use the instruction names, e.g.:
extern void _drvh(unsigned p); // set pin p as output and drive it high extern unsigned _testp(unsigned p); // test pin p for input // and so onNot every intrinsic will map to just one instruction, e.g. _coginit will have to do a "setq" for the parameter to pass to the COG. We'll probably also want accessor functions for things like the builtin COG registers, counter, and so forth.
@RossH , @"Dave Hein" , does this sound reasonable for Catalina and p2gcc respectively? If we three can agree on a naming scheme we can have it implemented in Catalina, p2gcc, fastspin, and the Risc-V emulator.
Regards,
Eric
Comments
I've been bumping into this issue with Spin2. I have yet to add the mode-specific smart pin intrinsics, but I'm thinking it will be necessary to have the new silicon in hand to make sure I've named things well and covered the functions properly. Having these in every language would go a long way to standardize things.
I'll make a list of what I've got, so far, and post it in this thread.
I totally agree when you say that there should be consensus. Regarding your point 1, I think the macro should be named "-D__propeller2__". It aligns perfectly with the name of the library, which is "propeller2.h".
On another note, functions that address pins should use the smallest variables possibles. For example, "_Bool getpin(unsigned char pin)" instead of "int getpin(int pin)". Or, another example, "void togpin(unsigned char pin)" instead of "void getpin(int pin)". I'm not assuming that getpin() and togpin() exist or will exist, but these are just examples of functions that don't need 32 bit integers. If I recall correctly, there are equivalent functions inside the existing "propeller.h" that use "int".
Better yet, the new lib could use int8_t, int16_t or int32_t (and also int64_t, if possible), when dealing with pins or memory addresses. It is more correct and very portable. It also shows your intention either as a developer or as a programmer. Although intx_t types correspond to unsigned long, int, short and char primitives, their correspondence to primitives always depends on the host and target system (tall order here, but feasible). In this case, being the P2 is always the target of choice, you can define the intx_t types accordingly, so that they have their fixed size as described (you only have to take into account if the host system is 32-bit or 64-bit, as "long int" size will vary, either 32-bit or 64-bit, IIRC). The types "size_t" and "ssize_t" could be implemented as well.
Kind regards, Samuel Lourenço
I'm going to have to disagree with you here. A minor point is that "_Bool" is a C99 invention, and if I recall correctly Catalina only supports C89; this could be worked around. The more serious concern is that "int" and "unsigned int" are by definition the natural and most efficient sizes for the platform. Casting them to/from "char" could require code to be inserted by the compiler. That is: needs to be compiled like: where the cast to (unsigned char) masks out the upper bits, and the cast back to (int) does zero extension. That's adding unnecessary instructions to the operation. "unsigned char" is already wider than we need, so it doesn't really add any error checking. I think we should allow the compiler the freedom to use the most efficient integer size. For current P2 compilers that's 32 bits, but one could imagine a compiler designed to save memory in which the most efficient size might be different. "int" and "unsigned int" are required by the standard to be at least 16 bits, but otherwise they're supposed to be whatever the compiler deems "best".
Regards,
Eric
On first glance it mostly looks ok. I am ok with predefining __propeller2__ on the P2 and with using an underscore to lead function names (this is pretty standard for C).
I am not so sure about using structures. Catalina is fine with this, but it might impede users coming from Spin. I think we should probably just stick with "unsigned int" for compatibility reasons. Sophisticated C users can use unions (which can be defined in the header files) to decode the values, but the functions themselves should return the same values as the Spin equivalents.
If you want to drive people away from using C on Propeller, by all means use the ridiculous C99 types like "_Bool" and "uint32_t"
The structures would only be for functions which return multiple values. Spin2 allows a function to return more than one result; for example to rotate x,y by an angle you could write something like the following in Spin2: which would set both x and y to the 2 values returned by "rotxy". It looks like a few of the functions Chip has proposed are like this, and it makes sense in the context (where the CORDIC does return two 32 bit results). To do this in C I think we have a few choices:
(a) use a struct for the returned result:
This is the most natural way in C to represent a multi-valued return, I think, and produces good code in GCC. fastspin's struct handling in C is still pretty shaky, but if we go this way it'll give me more incentive to get that working properly
(b) pass a pointer for one of the results: This is also fairly natural C, and works well if _rotxy is a function, but it does make for memory traffic so is potentially less efficient (the "struct" version can keep everything in registers, but this one has to force y into HUB memory).
(c) return a 64 bit result in "unsigned long long", with the first result in the low 32 bits and second in the high 32 bits: This should also be quite efficient, but it only handles the case of two returned values. This may be all we need, but in principle Spin2 can allow for more than that (I'm not sure what the limit is in Chip's compiler, but fastspin allows up to eight). I'm also not sure if we should force all C compilers for P2 to implement "unsigned long long" as 64 bits.
Another possibility is that we could hide the actual method of multiple assignment "under the hood" with a macro, something like: I'm a little uncomfortable with this because it looks like a function call but doesn't act like one (the first arguments are modified even though they don't have & in front of them). If we were working in C++ we could use references so it wouldn't be an issue, but I want the header file to work in plain C as well.
My own preference is for option (a), but I'm not married to any of them. If the other compiler writers all agree on some other standard then I'll go along with the consensus.
Yes, in general we want an abstract API, so this discussion is a subset of that one. It seems like a tractable subset. i.e. one that we can perhaps come to agreement on fairly quickly, I hope.
I actually have a use case right now for propeller2.h. I have a VGA driver that I'd like to have working in all of the C compilers; I've got it working in fastspin, RiscV, and p2gcc, but I had to hack up a header file for p2gcc, and then realized that I had no idea how to port it to Catalina as well. Having a single header file that would cover all 4 compilers would make this much nicer.
Sure. Use whatever names you like.
Aargh! I didn't know about that. That is really ugly. Spin needs proper types, not this kind of typographical workaround for their absence.
So yes, I guess we can at least ameliorate this madness in C by using functions that return structures. There is a good reason why this facility is rarely used in C, but in this case I guess it is a better solution than any of the alternatives
That makes sense. I'll be using the functions that the library has to offer, instead of changing the registers myself, then. I've made the wrong assumption that my code would be more efficient. Actually, I've resorted to recreate functions to change pins just because the ones in the library used "int" types, in the line of that wrong assumption.
Why is it ridiculous. Ridiculous it to have a standard that vaguely describes the size of ints. And then you get things like longs having 32-bit or 64-bit, depending on the machine. I'm aware that uintx_t types are derived from these primitives, but they are a step in the right direction. As an example, check the code attached. Would you implement this using primitives in which the size is not guaranteed to be known?
Kind regards, Samuel Lourenço
Good example. The type "unsigned char" must exist according to the C standard. The type uint8_t need not. In the case of your code, you would of course simply redefine uint8_t to be unsigned char, and probably get away with it.
But why should you need to do so?
Beauty is in the eye of the beholder. How would proper types resolve the issue of needing to return multiple values when rotating cartesian coordinates?
About the primitive sizes, the only thing that the C specification guarantees that a char is the smallest type (can be signed or unsigned, signedness is ambiguous with chars without the "signed" or "unsigned" modifiers). A short can larger or equal in size to a char. An int can be larger or equal in size to a short. A long can be larger or equal in size to an int. A long long can be larger or equal in size to a long. This is pretty ambiguous, and a nightmare to deal with. Thus, long has the same size of an int (32-bit) on a 32-bit machine, and has the same size of a long long (64-bit) on a 64-bit machine. If I recall correctly, int can have the same size of a short (16-bit) on a 16-bit machine. Caveats, caveats, caveats!
Having said that, I understand why many people avoid uintx_t types, or _t types in general, like the plague: essentially because they are derived types. But by a different reason and in the same measure, ambiguous types like int, short or long, should be avoided, especially at low level, hardware related stuff. Each type is useful on its own context.
Kind regards, Samuel Lourenço
Anyway, most users are not dumb, and those that are don't have the capacity to learn anything, no matter how much "simplified" is the language or use. Plus, inclusivity and over-simplification promotes lack of versatility and stifles creativity. Many people use their Arduinos to blink a LED, in a 555 timer fashion, and not much more than that. I see far more potential on the P2.
Kind regards, Samuel Lourenço
Let's not get into language wars. I know that C can do everything that Spin2 can do (and vice versa!), because I've written compilers for P2 for both of them. Some things are easier to express in C, some are easier to express in Spin2. Not every person likes every language, and not every language is the "best" tool for every task. Anyway, it's all moot -- this discussion is specifically about making the various C compilers for P2 work well together, so the C language is a given here. If you don't like C, please avoid this thread
Speaking for myself, I wouldn't be willing to develop for P2 without C, for two reasons:
- C is like a second language for me, and a good platform is provided for Propeller;
- Spin requires me to learn it, and if I'm not mistaken, it is interpreted using bytecodes (not my cup of tea).
In a nutshell, C is a mandatory requisite for me. Without it, I wouldn't consider P2 as an option. In fact, non-proprietary C support was one of the main reasons I choose P1.
kind regards, Samuel Lourenço
I think you are on the right track. I cannot comment about C since I've never really done much C code.
Chip is going to implement the multiple return values in spin2 because its required.
(x,y) = some_call(a,b)
Python also has a similar set of calls from little I know yet - still learning as I go with my work job.
So to me, it would make sense to support this type of call.
And yes please, use the same propeller.h header for all the languages.
Fastspin is fast becoming the compiler of choice to compile spin, C, micropython, basic, and spin2 shortly. This amazing effort is bringing all these languages together, and can output pasm or pasm2, and micropython, and hopefully spin2 bytecode in the future. Let's get behind Eric who is doing amazing things here
Sure you can have C functions that modify or operate on more than one variable at the same time (by passing these variables by address). The call would be different. It would be something in the line of "some_call (a, b, &x, &y)", being some_call a void function, and "x", "y" the output variables that are being passed by address. Alternatively, you can have "x = some_call_x (a, b); y = some_call_y (a, b)" if you don't mind having separate functions for x and y.
Regarding your last point, mind that "propeller.h" is C/C++ specific. You can write similar headers/include files for other languages though, but they will be specific for the language they target. You can't therefore have the same header/include file universal to every language. You can have, and should have, the same header file fit for different C/C++ compilers, if that's what you mean.
Kind regards, Samuel Lourenço
For now, I'd like to be able to write some C code for the P2 that all the compilers (Catalina, fastspin, p2gcc, RiscV gcc) can use. If we can make the standard C functions for P2 as similar as possible to what Chip's doing for Spin2, so much the better -- it will lessen the learning curve for those going between those languages.
The struct style "IntVec2 foo(int x,int y)" is perhaps the most nice-looking and helps reduce the amount of variables that need declaring ("IntVec2 pos1,pos2;" instead of "int pos1x,pos1y,pos2x,pos2y;"). OTOH, in C, you can't overload a version that takes the struct as it's parameter, so you'd have to do some minor jank like this: "pos2 = foo(pos1.x,pos1.y)"
The "pass two pointers" style "void foo(int x, int y, int *xr, int *yr)" is also good, since it is very flexible (you can write the results into spereate variables, a struct, an interleaved array, two seperate arrays, etc, all without too much jank). Though it requires more work on the compiler side to optimize out the hub access where possible.
The "pass one pointer, return the other" style "int foo(int x, int y, int *yr)" is annoyingly asymetric (for working with vectors where the elements are roughly equivalent), but otherwise the same as "pass two pointers".
The "return a 64 bit value" style "long long foo(int x,int y)" is just terrible and offers no advantage over the struct-return (IIRC GCC implements oversized types similarly to structs, unless the ABI says otherwise)
You have answered your own question. The function accepts - and returns - a "cartesian coordinate", not arbitrary values.
In most languages, you would define a "cartesian coordinate" type.
It is not "required", it is a syntactic fix for a language deficiency. But it breaks part of the implicit contract that a computer language - any language - has with the programmer. In this case, the implicit contract defined by having "functions" at all.
To see why, consider a function call like:
x = f1(a, f2(b,c), f3(d,e), f4(f)).
Does f1 take 4 parameters? You can no longer tell at a glance, because you don't know how many values the other functions return. You can''t even tell which parameter f3 supplies. It could be the third, the fourth, the third and fourth, or the fourth and the fifth. You need to look up the definition of every preceeding function in the call to figure this out. And if you ever change the number of parameters a function returns, you will need to go searching for every use of that function, to see what you may break. The problem gets worse if your language allows variadic functions, because the compiler will not be able to help you.
Also, are you really gaining anything? How do you use just one of the results of a function that returns two values (a common requirement - think of q,r=div(a,b) where in most cases you only want one of q or r). So do you have 3 separate functions? - i.e. one that returns just q, one that returns just r, and one that returns both? Or do you add a selector function that selects which result you want? e.g. q = select(div(a,b), 1), r = select(div(a,b),2)
Note that I am assuming that a function that returns two values can be used in place of two parameters. If it cannot, then that breaks the implicit function contract in a different way, which is that a function result can be used anywhere where the type of value it returns can appear. Essentially, we have introduced two non-interchangeable types of functions.
Either way, it may look pretty at first glance, but in fact it gets ugly fast
Then you can call it essentially by doing so, for example:
Note that rotate() stores the results in x_r, y_r. Their addresses are passed to the function, that then changes their values. In the function definition, the function takes two int vars x and y, which are the initial cartesian coordinates that have as values 90 and 60 respectively. It also accepts two int pointers *xr and *yr that are to be supplied with addresses of the variables x_r, y_r, where you store the results.
If you don't need to preserve the original coordinates after rotation, you can simplify the declaration:
And the example call as well:
The values are also fed via the same variables in which the results are stored. However, this is over-simplified, and a bit far from ideal, IMHO.
kind regards, Samuel Lourenço
which would then need to be packed and unpacked by both calling and called functions.
I think "most languages" is a difficult claim to make or prove, and it probably isn't worth the effort.
"x" and "y" are neither more nor less ugly than "p.x" and "p.y", but whether they make more or less sense depends very much on your previous programming experience.
Defining a struct for a point with elements x & y, and making the function accept and return these structs as parameters is essentially the same thing as defining a "Cartesian coordinate" type, yet it isn't even what was being proposed.
Why pass "arbitrary values" x & y into the function and return a struct and have to unpack it to "arbitrary values"?
when it should really be more like:
Consistency in implementation is more beautiful to me than any particular implementation.
I did really like the idea of multiple return parameters, but never thought of the implication of using the return values as parameter.
Being more on the spin side as C I just assumed longs and that they then replace n parameter. But parsing that might be a pain, not just for the compiler but for the guy reading the code. I have that problem sometimes, 'what the hell was this guy thinking' when reading code, sadly even with code I wrote by myself.
(a,b):=qrot(c,d) is easy to read and understand.
x = f1(a, f2(b,c), f3(d,e), f4(f)). as in his example not so much.
Maybe the solution is to allow structs and types in spin?
Implicit long if not declared to be compatible with old Spin, else done like types in fastspin basic?
Floating point support in Spin would make sense, having cordic and such, fastspin has the support already for Basic and C, it would just need to define a syntax for spin and get chip to use that too.
just. What do I know how much work that is, I just assume that @ersmith and @RossH can do magic.
I personally would prefer a RETURN A,B compared to defining return parameters as output in the function description.
Mike
After all, if f2() returned a struct containing multiple values, or a pointer to a memory block containing multiple values, you still can't really tell how many parameters f1() actually takes, without examining the code of f1().
I don't think it's a valid assumption.
In your example there is some sense to using only quotient or remainder, or both; but for the rotation there are far fewer examples where that makes sense.
If f2() above actually took "b" as input, and "c" was a pointer to a secondary output, how can a casual glance inform the programmer what f1() will do with the result stored in c?
C is not immune to getting ugly fast either....
Edit: clause in italics added.