I actually don't have multiple return values in Spin2. I just have '(a,b,c,...) := (x,y,z,...)' and a few instructions which return two values to be assigned as such: '(x,y) := ROTXY(x,y,angle)'. I don't really want to make functions return multiple values. Group assignments are nice, though.
I actually don't have multiple return values in Spin2. I just have '(a,b,c,...) := (x,y,z,...)' and a few instructions which return two values to be assigned as such: '(x,y) := ROTXY(x,y,angle)'. I don't really want to make functions return multiple values. Group assignments are nice, though.
Can you define your own functions that return two or more values? Or are ROTXY etc "special"?
Thanks for the kind words, @Cluso99 ! Just to clarify though: fastspin and micropython are completely separate projects. Eventually it would be nice to have some level of Python support in fastspin/spin2cpp (e.g. an ability to convert Spin2 objects to Python objects) but that's for the future.
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.
Didn't realise the micropython was not part of fastspin.
BTW I don't think we'd want to go from spin to python. More likely to go the other way though. But if the langauge were similar, and it almost seems to be, then the output could be either, at compile time.
The couple of things I've particularly noticed is the ":" after an if/elif/else/loops and similar. Could be optional for both spin and python.
def is equivalent to pub and pri.
There are a few quirks that would bite...
ranges(0:n) only execute to n-1 and not n
There is no case/switch - yep, not implemented in python We might just put that one in to be compatible with spin.
This makes more sense to me. In particular, the example of division where both quotient and remainder (do I have them correct?) are returned... If you do it separately, then it will take a lot longer to calculate both because you have to run the division twice. Big time penalty here.
I actually don't have multiple return values in Spin2. I just have '(a,b,c,...) := (x,y,z,...)' and a few instructions which return two values to be assigned as such: '(x,y) := ROTXY(x,y,angle)'. I don't really want to make functions return multiple values. Group assignments are nice, though.
Can you define your own functions that return two or more values? Or are ROTXY etc "special"?
They are special. However, the compiler may support custom extended bytecodes, where you attach some hub code via a word vector and let the compiler know the name of the instruction, how many parameters, and how many return values it has.
Or, as Wuerfel_21 pointed out, you can have a function that is declared as this:
void rotate(int x, int y, int *xr, int *yr)
Then you can call it essentially by doing so, for example:
int x_r, y_r;
rotate(90, 60, &x_r, &y_r);
printf("Result is (%d, %d)\n", x_r, y_r)
There are two disadvantages to this style of function:
(1) Since x_r and y_r are being passed by reference, they cannot be kept in registers, but must be placed on the stack (at the very least around the call to "rotate"). That means that operations on them are much slower, and require read/modify/write instead of direct access.
(2) Once a variable has its address taken, then any time a pointer reference occurs then that variable is potentially involved. This makes optimization hard. For a trivial example, consider:
x = b + c;
y = b + c;
Normally the compiler can re-write this as:
x = b + c;
y = x;
but now consider
x = b + c;
*ptr = 1;
y = b + c;
If we've ever before seen "&b" or "&c" (or if we're in a loop and see those expressions later in the function) then we have no idea whether "*ptr = 1" may have changed the value of b or c. So we can no longer eliminate the common sub expression.
This is just a toy example, but there are actually a lot of optimizations that rely on knowing when variables have changed, and any use of pointers makes this very, very hard.
So for the best code we should try to avoid APIs that require the use of pointers.
I didn't want to get into a language discussion, but it appears we're there, so...
Multiple return values is a language feature that's becoming more common, and it's a very useful tool. C *already* has a very limited form of this; a void function returns 0 values, other functions return 1 value. So it's already necessary to know the return types of functions to know whether f(g(x)) is a valid construction. (Not to mention that if g returns a pointer to float but f expects a pointer to int then very bad things can happen if you write something like that.) Compared to those issues the question of how many variables the function returns is not difficult at all, and there are many ways to handle this in the language. If f returns two values a,b then f(g(x)) could be interpreted as:
a,b := g(x)
f(a, b) ' use all return values
or
a,b := g(x)
f(a) ' use just the first return value
or it can just be disallowed completely (just as you cannot pass a void return value to a function, neither can you pass multiple return values).
Any of these choices are logical. I know you don't like the first one Ross, but it's work-able. The compiler already keeps track of how many arguments there are, so it will report an error if the total numbers don't add up. And the programmer should know what the individual functions are doing. Yes, there's room for shooting yourself in the foot, but Spin (and C) already provide a very large number of ways of doing that, and neither language is particularly designed with the goal of protecting the programmer from him/her self. (There are other languages that are so designed, of course.)
Chip, if you already have the facility there for supporting multiple return values then you may want to consider letting user functions do this as well. I've certainly found places in the fastspin libraries where I've chosen to write in the fastspin dialect of Spin2 rather than C or BASIC precisely because using multiple return values made certain operations easier or more efficient to write. The floating point code is one example: it got much faster and easier to read when I could split floats up using multiple return values rather than having to pass pointers.
Any of these choices are logical. I know you don't like the first one Ross, but it's work-able.
Yes, it is workable. Anything is workable if you (and your customers!) are prepared to work hard enough
In C, we can resolve the current issue by introducing a "coordinate" type consisting of two longs. This is the correct solution from the perspective of a typed language, and I don't think we need to do any more than that at this point - certainly not on the basis of a feature that may or may not even eventuate.
In C, we can resolve the current issue by introducing a "coordinate" type consisting of two longs. This is the correct solution from the perspective of a typed language, and I don't think we need to do any more than that at this point - certainly not on the basis of a feature that may or may not even eventuate.
Yeah, sorry if I wasn't being clear -- I was *not* proposing to change the C language (!) and so for propeller2.h I agree that the best solution in C for multiple return values from a function is to define a struct and use the ability of C to manipulate types.
I'm just pointing out that some other languages, even typed languages, do have multiple return values and that such a feature in a language can be well-defined and useful. The Go language, for example, makes heavy use of multiple return values for error handling -- most of the standard I/O routines there return two values: the count of items read or written and also an error indication. That's much cleaner than C's method of returning -1 and setting a global variable to indicate which error occured.
I don't like the term returning multiple values. In C it's called returning a handle like fopen or fdserial. We don't call them returning a pointer to a structure even though that is what is happening.
I also don't like python as 90 percent of the "objects" used in python are C programs imported into the language. It gives developers a way to use C programs like the old BASIC programs without loosing all the performance of C. I found the retuning of multiple values in Python very confusing and often times was looking at the wrong returned value.
Keeping the function names the same as Spin is great but that is as far as I go.
A quick blast at putting together a "propeller2.h" ...
#ifndef __PROPELLER2__H
#define __PROPELLER2__H
/*
* common definitions
*/
#define MAX_COG 0x08
#define ANY_COG 0x10
/*
* common types
*/
// general type used to match Spin LONG (n.b. we don't use 'LONG'
// as there is too much chance for confusion with C type 'long')
typedef unsigned long ULONG;
// type for cartesian coordinates
typedef struct _cartesian {
ULONG x, y;
} CARTESIAN;
// type for polar coordinates
typedef struct _polar {
ULONG r, t;
} POLAR;
/*
* P2 32 Bit Clock Mode (see macros below to construct)
*
* 0000_000e_dddddd_mmmmmmmmmm_pppp_cc_ss
*
* e = XPLL (0 = PLL Off, 1 = PLL On)
* dddddd = XDIV (0 .. 63, crystal divider => 1 .. 64)
* mmmmmmmmmm = XMUL (0 .. 1023, crystal multiplier => 1 .. 1024)
* pppp = XPPP (0 .. 15, see macro below)
* cc = XOSC (0 = OFF, 1 = OSC, 2 = 15pF, 3 = 30pF)
* ss = XSEL (0 = rcfast, 1 = rcslow, 2 = XI, 3 = PLL)
*/
// macro to calculate XPPP (1->15, 2->0, 4->1, 6->2 ... 30->14) ...
#define XPPP(XDIVP) ((((XDIVP)>>1)+15)&0xF)
// macro to combine XPLL, XDIV, XDIVP, XOSC & XSEL into a 32 bit CLOCKMODE ...
#define CLOCKMODE(XPLL,XDIV,XMUL,XDIVP,XOSC,XSEL) ((XPLL<<24)+((XDIV-1)<<18)+((XMUL-1)<<8)+(XPPP(XDIVP)<<4)+(XOSC<<2)+XSEL)
// macro to calculate final clock frequency ...
#define CLOCKFREQ(XTALFREQ, XDIV, XMUL, XDIVP) ((XTALFREQ)/(XDIV)*(XMUL)/(XDIVP))
void _CLKSET(ULONG clkmode, ULONG clkfreq);
void _HUBSET(ULONG val);
ULONG _COGINIT(ULONG cog, ULONG pgm, ULONG ptr);
void _COGSTOP(ULONG cog);
ULONG _COGID(void);
ULONG _COGCHK(ULONG cog);
ULONG _LOCKNEW(void);
void _LOCKRET(ULONG lock);
ULONG _LOCKTRY(ULONG lock);
void _LOCKREL(ULONG lock);
ULONG _LOCKCHK(ULONG lock);
void _COGATN(ULONG mask);
ULONG _POLLATN(void);
void _WAITATN(void);
CARTESIAN _ROTXY(CARTESIAN coord, ULONG t);
CARTESIAN _POLXY(POLAR coord);
POLAR _XYPOL(CARTESIAN coord);
ULONG _RND(void);
ULONG _CNT(void);
ULONG _POLLCNT(ULONG tick);
void _WAITCNT(ULONG tick);
void _WAITx(ULONG tick);
void _PINW(ULONG pin, ULONG val);
void _PINL(ULONG pin);
void _PINH(ULONG pin);
void _PINNOT(ULONG pin);
void _PINRND(ULONG pin);
void _PINF(ULONG pin);
ULONG _PIN(ULONG pin);
void _WRPIN(ULONG pin, ULONG val);
void _WXPIN(ULONG pin, ULONG val);
void _WYPIN(ULONG pin, ULONG val);
void _AKPIN(ULONG pin);
ULONG _RDPIN(ULONG pin);
ULONG _RQPIN(ULONG pin);
void _BYTEMOVE(ULONG dst, ULONG src, ULONG cnt);
void _BYTEFILL(ULONG dst, ULONG val, ULONG cnt);
void _WORDMOVE(ULONG dst, ULONG src, ULONG cnt);
void _WORDFILL(ULONG dst, ULONG val, ULONG cnt);
void _LONGMOVE(ULONG dst, ULONG src, ULONG cnt);
void _LONGFILL(ULONG dst, ULONG val, ULONG cnt);
ULONG _STRSIZE(ULONG adr);
ULONG _STRCOMP(ULONG adra, ULONG adrb);
#endif
All comments, suggestions etc welcome!
Ross.
Looks good, RossH.
I'm getting Spin2's external-code scratchpad area moved from $140..$14F to $1E0..$1EF. It looks like this:
1E0: RESULT
1E1..1Ex: Parameters
1Ex..1EF: Local variables
So, this can be used however one would like, but in Spin2 it's set up with the RESULT/Parameters/Locals before the CALL and the scratchpad is written back to RESULT/Parameters/Locals upon return from the CALL.
I'm getting Spin2's external-code scratchpad area moved from $140..$14F to $1E0..$1EF. It looks like this:
1E0: RESULT
1E1..1Ex: Parameters
1Ex..1EF: Local variables
So, this can be used however one would like, but in Spin2 it's set up with the RESULT/Parameters/Locals before the CALL and the scratchpad is written back to RESULT/Parameters/Locals upon return from the CALL.
Are you intending this to be used to allow C code to call Spin functions, or vice-versa? Or both?
I'm getting Spin2's external-code scratchpad area moved from $140..$14F to $1E0..$1EF. It looks like this:
1E0: RESULT
1E1..1Ex: Parameters
1Ex..1EF: Local variables
So, this can be used however one would like, but in Spin2 it's set up with the RESULT/Parameters/Locals before the CALL and the scratchpad is written back to RESULT/Parameters/Locals upon return from the CALL.
Are you intending this to be used to allow C code to call Spin functions, or vice-versa? Or both?
Just for Spin to call PASM code. Something calling Spin would be really complicated, because several variables would need to be set up. Not simple.
Just for Spin to call PASM code. Something calling Spin would be really complicated, because several variables would need to be set up. Not simple.
Oh, good (phew!)
You also need to specify which registers are available or not, and which are preset (e.g. PTRA is used as stack pointer etc).
Yes. These $1Ex registers are, at least, conduit. Registers $000..$13F are also free for some user purpose. PA/PB/PTRA/PTRB and the streamer are all available for use by the called code, plus PTRA points to the stack in case it's needed. Upon return, PTRA is reloaded with a saved value, the streamer is restarted with a fresh RDFAST and bytecode execution resumes.
Screaming capitalization seems unnecessary. Macros and global constants are one thing, but types and function names?
I also don't see the point in defining ulong, and would personally prefer to use uint32_t, even if that's not part of the C standard, but I don't feel too strongly on this one since use of that typedef does not prevent me from using uint32_t as parameters into or return values from those functions.
Screaming capitalization seems unnecessary. Macros and global constants are one thing, but types and function names?
I also don't see the point in defining ulong, and would personally prefer to use uint32_t, even if that's not part of the C standard, but I don't feel too strongly on this one since use of that typedef does not prevent me from using uint32_t as parameters into or return values from those functions.
Capitalization is a fairly common C convention for user-defined type names. I'm not wedded to it, but I prefer it to the old "_t" convention, which is now recommended to be restricted only to system-defined types (I believe POSIX compliance may even enforce this). This means, for instance, that if we adopted uint32_t and friends, we would still not be able to have polar_t or cartesian_t - which I think would be an unnecessary inconsistency.
ULONG is defined at all because a simple "long" type is not guaranteed by C to be 32 bits, and also because I thought ULONG would probably be more familiar to Spin users than uint32_t. We could use just plain LONG, but I thought that would be too prone to be confused with a C long.
I can not really follow why you want to distinguish 'polar' and 'cartesian' tuples of longs, it is just two longs returned together and accessible each as 32bit.
The current idea with multiple return vales is that they basically are a list of longs instead of just one long, different types is not the solution, structures might be better for understanding.
I think Chip and Eric support up to 8 returned long values, so maybe, if you want types instead a return1_t, return2_t,...
It is not just for build in Spin features, it should be usable from user defined functions also, and my two to be returned 32 bit values may be temperature and pressure of my engine, neither 'polar' nor 'cartesian'
I can not really follow why you want to distinguish 'polar' and 'cartesian' tuples of longs, it is just two longs returned together and accessible each as 32bit.
Short answer? Because this is supposed to be C, not Spin, and C has types!
Longer answer: Because it helps to differentiate whether the function accepts or returns polar or cartesian coordinates **. This way, the compiler can tell you if you get it wrong.
** if your function actually just accepts or returns two longs, you probably shouldn't be using either type!
ULONG is defined at all because a simple "long" type is not guaranteed by C to be 32 bits, and also because I thought ULONG would probably be more familiar to Spin users than uint32_t. We could use just plain LONG, but I thought that would be too prone to be confused with a C long.
What missing from ULONG, is any indication of bit-count.
That's what makes uint32_t appealing, is there is uint8_t, and on some systems uint64_t & all are very clear as to their sizes, on any platform.
ULONG is defined at all because a simple "long" type is not guaranteed by C to be 32 bits, and also because I thought ULONG would probably be more familiar to Spin users than uint32_t. We could use just plain LONG, but I thought that would be too prone to be confused with a C long.
What missing from ULONG, is any indication of bit-count.
That's what makes uint32_t appealing, is there is uint8_t, and on some systems uint64_t & all are very clear as to their sizes, on any platform.
I think uint32_t is quite unmistakable in meaning. It's a little verbose, but concise.
ULONG is defined at all because a simple "long" type is not guaranteed by C to be 32 bits, and also because I thought ULONG would probably be more familiar to Spin users than uint32_t. We could use just plain LONG, but I thought that would be too prone to be confused with a C long.
What missing from ULONG, is any indication of bit-count.
That's what makes uint32_t appealing, is there is uint8_t, and on some systems uint64_t & all are very clear as to their sizes, on any platform.
I think uint32_t is quite unmistakable in meaning. It's a little verbose, but concise.
I like the idea of defining shorthands for the fixed-width types (example taken from libOGC's gctypes.h)
Not sure if you'd want to put such definitions in a system header.
That's what makes uint32_t appealing, is there is uint8_t, and on some systems uint64_t & all are very clear as to their sizes, on any platform.
Anyone is free to use these types if they want. For instance, if your compiler supports it, you can use a uint32_t anywhere you could use a ULONG. All the current Propeller C compilers will be ok either way, and most likely all future ones will as well.
But one of the things we are trying to achieve here is some kind of C/Spin/<insert any other language here> language compatibility, so unless we can get Spin to change, I am now leaning back towards avoiding the whole issue and just using LONG (and also, if the need arises, WORD and BYTE). This is perfectly acceptable but is generally "not recommended" - but precisely for that reason we are unlikely to ever run into a name collisions if we use such names here. And it would make C code look a lot more like Spin.
It really comes down to whether we are aiming this header file at C users or Spin users. If the former, then uintN_t might indeed be preferable. If the latter, then LONG, WORD and BYTE would be preferable.
Of course, since they would all be interoperable in practice, we could simply have two header files, and you use whichever one you prefer!
Language standards come first. If a language has a standard (think Python's PEP) or overwhelming convention, always follow that over any convention in the language agnostic API. For instance, I used camelCase for parameter names but PEP dictates underscores. The Python implementations should therefore use underscore for all parameter names.
On a similar note, C and Spin don't have exceptions and therefore must report errors through a return code (please don't crucify me if that's incorrect about Spin... I haven't written Spin in a long time). However, the Python implementation should use the Pythonic way of handling errors: raise an exception and DO not return an "error code."
BTW, this entire thread is a great example of something that could be a pull request to https://github.com/DavidZemon/ParallaxAbstractionLayer
The readme doesn't make it clear where where the file should be placed in the repository, but I'll get that updated today.
Screaming capitalization seems unnecessary. Macros and global constants are one thing, but types and function names?
I also don't see the point in defining ulong, and would personally prefer to use uint32_t, even if that's not part of the C standard, but I don't feel too strongly on this one since use of that typedef does not prevent me from using uint32_t as parameters into or return values from those functions.
Capitalization is a fairly common C convention for user-defined type names. I'm not wedded to it, but I prefer it to the old "_t" convention, which is now recommended to be restricted only to system-defined types (I believe POSIX compliance may even enforce this). This means, for instance, that if we adopted uint32_t and friends, we would still not be able to have polar_t or cartesian_t - which I think would be an unnecessary inconsistency.
ULONG is defined at all because a simple "long" type is not guaranteed by C to be 32 bits, and also because I thought ULONG would probably be more familiar to Spin users than uint32_t. We could use just plain LONG, but I thought that would be too prone to be confused with a C long.
Still, your suggestions have merit.
Thanks for the proposal, @RossH . I generally like it. The biggest change I would wish for is in the capitalization of function names -- for me that seems to imply a macro or type name rather than a function name. Of course these could be implemented as macros, but still, it just doesn't seem C-like to me. So I'd prefer _pinh, _pinl, etc. to _PINH, _PINL, etc. It's just my preference though, perhaps others feel differently.
Other very minor issues:
(a) Since we're the compiler/library writers, we're allowed to use the "_t" name space, and in fact I read the C99 standard as encouraging this. If we use "polar_t" then it is guaranteed not to conflict with any user variables (assuming the user follows the rules), whereas POLAR doesn't have this guarantee. It's a minor point, since the new header file is Propeller specific anyway, but I guess it could come up if we ever find it useful to include <propeller2.h> in the implementation of some other standard header file.
(b) For the same reason I slightly prefer the C99 type names like "uint32_t" over anything new like ULONG. Actually for anything like locks or cog ids where we don't need to specify the bit width I'd be fine with just "int" or "unsigned int". There's no need to over-specify: a lock, for example, will fit in an 8 bit variable just fine, so I'd be happy to let the compiler use whatever size it wants for this, which is exactly what "int" is for.
(c) There is some merit in using LONG, WORD, BYTE to mimic the way Spin does things. On the other hand we are writing a C header file and users should be familiar enough with the C language to know that "int32_t", "uint16_t", and "uint8_t" are the C names for those things (when the bit sizes matter) or that "int" / "short" / "char" are the names to use when the exact size isn't critical. Again, this'll all come out in the wash so it'll work either way.
Oh, another thing: I would probably skip _STRSIZE and _STRCOMP, since C already has strlen and strcmp. Again, not a big deal if we have them in, it won't hurt anything, but my preference is to encourage use of the standard library functions over Propeller specific ones.
What if (most of) propeller2.h wasn't included in any C compiler, but instead was a library that everyone included in their projects? I'm thinking about the way Linaro does GCC. A quick poke around /usr/lib/gcc/arm-none-eabi/7.3.1/include on my Ubuntu 19.04 distribution with the gcc-arm-none-eabi package installed indicates that the only ARM-specific headers define C function wrappers around all of the ARM assembly instructions. No easy-to-use pin manipulation, no clock setting, nothing meant to be user-friendly. If we want our GCC or LLVM port to be accepted upstream, keeping the Propeller-specifics to a minimum is probably a good idea. Make "propeller2.h" just a list of all the assembly instructions, referencing __buildin_propeller2_<some instruction>() and then put the user-friendly library elsewhere.
So let's get back to the question: what if the above version of propeller2.h wasn't included in the compiler? Where else would it go? How would developers get it, and how would they make it available to their build system?
I'd prefer to propose answers to the above questions with the following in mind: keep is easy for the common case, and possible for the rest.
Where would it go?
I'd propose that the contents of propeller2.h (which may not remain all in the same file) be moved to its own Git repository. This repository would be a C-implementation of the Parallax Abstraction Library.
How would developers get it?
Parallax's educational customers would receive a simple all-in-one package - similar to SimpleIDE today - that includes the compiler and Propeller 2 C standard library. They would hardly know the difference between GCC and the Propeller 2 C standard library, just like most of them don't know the difference between SimpleIDE, GCC, and the Simple libraries.
How would "advanced" users add it to their own build system?
CLI users (or anyone who wants to use GCC + P2 outside SimpleIDE2) would download the Propeller 2 C standard library independently of the compiler (preferably in pre-compiled format, from a CI server) and would be writing their own Makefile/CMake script/whatever which adds the library's include path to the compiler's header include search path. Examples could/would/should be provided in the Propeller 2 C standard library README.
If we use "polar_t" then it is guaranteed not to conflict with any user variables (assuming the user follows the rules)
This implies (and is confirmed on https://www.gnu.org/software/libc/manual/html_node/Reserved-Names.html) that users should not be using the "_t" naming convention for types, and my above proposal would make the Propeller 2 C standard library just a normal "user" excluding it from the "_t" convention. Not sure what to do about that.
There's the option of ignoring the recommendation, and using _t anyway. Lots of people do it. Alternatively, PascalCase (camelCase with leading capital letter) seems reasonably common. Suffixing with "Type" or "_Type" or "_type" is another... though that seems excessively verbose and unnecessary.
And of course, there is always the SCREAMING_CAPITALS option... though I prefer PascalCase or any of the Type/_Type/_type suffixes to all caps.
ULONG is defined at all because a simple "long" type is not guaranteed by C to be 32 bits, and also because I thought ULONG would probably be more familiar to Spin users than uint32_t. We could use just plain LONG, but I thought that would be too prone to be confused with a C long.
What missing from ULONG, is any indication of bit-count.
That's what makes uint32_t appealing, is there is uint8_t, and on some systems uint64_t & all are very clear as to their sizes, on any platform.
I think uint32_t is quite unmistakable in meaning. It's a little verbose, but concise.
I like the idea of defining shorthands for the fixed-width types (example taken from libOGC's gctypes.h)
Not sure if you'd want to put such definitions in a system header.
I think we could import "stdint.h" from the standard library for that. But if that's not possible, it could be defined on "propeller.h". Then, for safety, to use it, one could use "#define P2TYPES" or something, to include them.
Screaming capitalization seems unnecessary. Macros and global constants are one thing, but types and function names?
I also don't see the point in defining ulong, and would personally prefer to use uint32_t, even if that's not part of the C standard, but I don't feel too strongly on this one since use of that typedef does not prevent me from using uint32_t as parameters into or return values from those functions.
Capitalization is a fairly common C convention for user-defined type names. I'm not wedded to it, but I prefer it to the old "_t" convention, which is now recommended to be restricted only to system-defined types (I believe POSIX compliance may even enforce this). This means, for instance, that if we adopted uint32_t and friends, we would still not be able to have polar_t or cartesian_t - which I think would be an unnecessary inconsistency.
ULONG is defined at all because a simple "long" type is not guaranteed by C to be 32 bits, and also because I thought ULONG would probably be more familiar to Spin users than uint32_t. We could use just plain LONG, but I thought that would be too prone to be confused with a C long.
Still, your suggestions have merit.
Thanks for the proposal, @RossH . I generally like it. The biggest change I would wish for is in the capitalization of function names -- for me that seems to imply a macro or type name rather than a function name. Of course these could be implemented as macros, but still, it just doesn't seem C-like to me. So I'd prefer _pinh, _pinl, etc. to _PINH, _PINL, etc. It's just my preference though, perhaps others feel differently.
Other very minor issues:
(a) Since we're the compiler/library writers, we're allowed to use the "_t" name space, and in fact I read the C99 standard as encouraging this. If we use "polar_t" then it is guaranteed not to conflict with any user variables (assuming the user follows the rules), whereas POLAR doesn't have this guarantee. It's a minor point, since the new header file is Propeller specific anyway, but I guess it could come up if we ever find it useful to include <propeller2.h> in the implementation of some other standard header file.
(b) For the same reason I slightly prefer the C99 type names like "uint32_t" over anything new like ULONG. Actually for anything like locks or cog ids where we don't need to specify the bit width I'd be fine with just "int" or "unsigned int". There's no need to over-specify: a lock, for example, will fit in an 8 bit variable just fine, so I'd be happy to let the compiler use whatever size it wants for this, which is exactly what "int" is for.
(c) There is some merit in using LONG, WORD, BYTE to mimic the way Spin does things. On the other hand we are writing a C header file and users should be familiar enough with the C language to know that "int32_t", "uint16_t", and "uint8_t" are the C names for those things (when the bit sizes matter) or that "int" / "short" / "char" are the names to use when the exact size isn't critical. Again, this'll all come out in the wash so it'll work either way.
Thanks,
Eric
I don't agree with point A, because the reason why it won't collide with user defined types it is because "_t" types are system reserved types, normally defined in "stddef.h" and "stdint.h". Users won't typically create variable types ended in "_t" because of this.
Regarding point B, and C, I totally agree. And since we are programming in C, it makes sense to use the C99 naming conventions instead of the ones used for Spin.
Comments
PRI rotate_xy(x,y,a)
(long[x], long[y]) := ROTXY(long[x], long[y],a)
Usage would be...
rotate_xy(@x,@y,a)
Maybe we could simply use '[address]' to mean 'long[address]'...
PRI rotate_xy(x,y,a)
([x], [y]) := ROTXY([x], [y],a)
Can you define your own functions that return two or more values? Or are ROTXY etc "special"?
Didn't realise the micropython was not part of fastspin.
BTW I don't think we'd want to go from spin to python. More likely to go the other way though. But if the langauge were similar, and it almost seems to be, then the output could be either, at compile time.
The couple of things I've particularly noticed is the ":" after an if/elif/else/loops and similar. Could be optional for both spin and python.
def is equivalent to pub and pri.
There are a few quirks that would bite...
ranges(0:n) only execute to n-1 and not n
There is no case/switch - yep, not implemented in python We might just put that one in to be compatible with spin.
This makes more sense to me. In particular, the example of division where both quotient and remainder (do I have them correct?) are returned... If you do it separately, then it will take a lot longer to calculate both because you have to run the division twice. Big time penalty here.
They are special. However, the compiler may support custom extended bytecodes, where you attach some hub code via a word vector and let the compiler know the name of the instruction, how many parameters, and how many return values it has.
(1) Since x_r and y_r are being passed by reference, they cannot be kept in registers, but must be placed on the stack (at the very least around the call to "rotate"). That means that operations on them are much slower, and require read/modify/write instead of direct access.
(2) Once a variable has its address taken, then any time a pointer reference occurs then that variable is potentially involved. This makes optimization hard. For a trivial example, consider: Normally the compiler can re-write this as: but now consider If we've ever before seen "&b" or "&c" (or if we're in a loop and see those expressions later in the function) then we have no idea whether "*ptr = 1" may have changed the value of b or c. So we can no longer eliminate the common sub expression.
This is just a toy example, but there are actually a lot of optimizations that rely on knowing when variables have changed, and any use of pointers makes this very, very hard.
So for the best code we should try to avoid APIs that require the use of pointers.
Multiple return values is a language feature that's becoming more common, and it's a very useful tool. C *already* has a very limited form of this; a void function returns 0 values, other functions return 1 value. So it's already necessary to know the return types of functions to know whether f(g(x)) is a valid construction. (Not to mention that if g returns a pointer to float but f expects a pointer to int then very bad things can happen if you write something like that.) Compared to those issues the question of how many variables the function returns is not difficult at all, and there are many ways to handle this in the language. If f returns two values a,b then f(g(x)) could be interpreted as: or or it can just be disallowed completely (just as you cannot pass a void return value to a function, neither can you pass multiple return values).
Any of these choices are logical. I know you don't like the first one Ross, but it's work-able. The compiler already keeps track of how many arguments there are, so it will report an error if the total numbers don't add up. And the programmer should know what the individual functions are doing. Yes, there's room for shooting yourself in the foot, but Spin (and C) already provide a very large number of ways of doing that, and neither language is particularly designed with the goal of protecting the programmer from him/her self. (There are other languages that are so designed, of course.)
Chip, if you already have the facility there for supporting multiple return values then you may want to consider letting user functions do this as well. I've certainly found places in the fastspin libraries where I've chosen to write in the fastspin dialect of Spin2 rather than C or BASIC precisely because using multiple return values made certain operations easier or more efficient to write. The floating point code is one example: it got much faster and easier to read when I could split floats up using multiple return values rather than having to pass pointers.
Yes, it is workable. Anything is workable if you (and your customers!) are prepared to work hard enough
In C, we can resolve the current issue by introducing a "coordinate" type consisting of two longs. This is the correct solution from the perspective of a typed language, and I don't think we need to do any more than that at this point - certainly not on the basis of a feature that may or may not even eventuate.
Yeah, sorry if I wasn't being clear -- I was *not* proposing to change the C language (!) and so for propeller2.h I agree that the best solution in C for multiple return values from a function is to define a struct and use the ability of C to manipulate types.
I'm just pointing out that some other languages, even typed languages, do have multiple return values and that such a feature in a language can be well-defined and useful. The Go language, for example, makes heavy use of multiple return values for error handling -- most of the standard I/O routines there return two values: the count of items read or written and also an error indication. That's much cleaner than C's method of returning -1 and setting a global variable to indicate which error occured.
I also don't like python as 90 percent of the "objects" used in python are C programs imported into the language. It gives developers a way to use C programs like the old BASIC programs without loosing all the performance of C. I found the retuning of multiple values in Python very confusing and often times was looking at the wrong returned value.
Keeping the function names the same as Spin is great but that is as far as I go.
Mike
All comments, suggestions etc welcome!
Ross.
Looks good, RossH.
I'm getting Spin2's external-code scratchpad area moved from $140..$14F to $1E0..$1EF. It looks like this:
1E0: RESULT
1E1..1Ex: Parameters
1Ex..1EF: Local variables
So, this can be used however one would like, but in Spin2 it's set up with the RESULT/Parameters/Locals before the CALL and the scratchpad is written back to RESULT/Parameters/Locals upon return from the CALL.
Are you intending this to be used to allow C code to call Spin functions, or vice-versa? Or both?
Just for Spin to call PASM code. Something calling Spin would be really complicated, because several variables would need to be set up. Not simple.
Oh, good (phew!)
You also need to specify which registers are available or not, and which are preset (e.g. PTRA is used as stack pointer etc).
Yes. These $1Ex registers are, at least, conduit. Registers $000..$13F are also free for some user purpose. PA/PB/PTRA/PTRB and the streamer are all available for use by the called code, plus PTRA points to the stack in case it's needed. Upon return, PTRA is reloaded with a saved value, the streamer is restarted with a fresh RDFAST and bytecode execution resumes.
I also don't see the point in defining ulong, and would personally prefer to use uint32_t, even if that's not part of the C standard, but I don't feel too strongly on this one since use of that typedef does not prevent me from using uint32_t as parameters into or return values from those functions.
Capitalization is a fairly common C convention for user-defined type names. I'm not wedded to it, but I prefer it to the old "_t" convention, which is now recommended to be restricted only to system-defined types (I believe POSIX compliance may even enforce this). This means, for instance, that if we adopted uint32_t and friends, we would still not be able to have polar_t or cartesian_t - which I think would be an unnecessary inconsistency.
ULONG is defined at all because a simple "long" type is not guaranteed by C to be 32 bits, and also because I thought ULONG would probably be more familiar to Spin users than uint32_t. We could use just plain LONG, but I thought that would be too prone to be confused with a C long.
Still, your suggestions have merit.
The current idea with multiple return vales is that they basically are a list of longs instead of just one long, different types is not the solution, structures might be better for understanding.
I think Chip and Eric support up to 8 returned long values, so maybe, if you want types instead a return1_t, return2_t,...
It is not just for build in Spin features, it should be usable from user defined functions also, and my two to be returned 32 bit values may be temperature and pressure of my engine, neither 'polar' nor 'cartesian'
maybe just return a array of ulong?
Enjoy!
Mike
Short answer? Because this is supposed to be C, not Spin, and C has types!
Longer answer: Because it helps to differentiate whether the function accepts or returns polar or cartesian coordinates **. This way, the compiler can tell you if you get it wrong.
** if your function actually just accepts or returns two longs, you probably shouldn't be using either type!
yes, uint32_t is quite widely used now.
What missing from ULONG, is any indication of bit-count.
That's what makes uint32_t appealing, is there is uint8_t, and on some systems uint64_t & all are very clear as to their sizes, on any platform.
I think uint32_t is quite unmistakable in meaning. It's a little verbose, but concise.
I like the idea of defining shorthands for the fixed-width types (example taken from libOGC's gctypes.h)
Not sure if you'd want to put such definitions in a system header.
Anyone is free to use these types if they want. For instance, if your compiler supports it, you can use a uint32_t anywhere you could use a ULONG. All the current Propeller C compilers will be ok either way, and most likely all future ones will as well.
But one of the things we are trying to achieve here is some kind of C/Spin/<insert any other language here> language compatibility, so unless we can get Spin to change, I am now leaning back towards avoiding the whole issue and just using LONG (and also, if the need arises, WORD and BYTE). This is perfectly acceptable but is generally "not recommended" - but precisely for that reason we are unlikely to ever run into a name collisions if we use such names here. And it would make C code look a lot more like Spin.
It really comes down to whether we are aiming this header file at C users or Spin users. If the former, then uintN_t might indeed be preferable. If the latter, then LONG, WORD and BYTE would be preferable.
Of course, since they would all be interoperable in practice, we could simply have two header files, and you use whichever one you prefer!
From the bottom of http://forums.parallax.com/discussion/169878/language-agnostic-api-for-p2-hal-implementations/p1
BTW, this entire thread is a great example of something that could be a pull request to https://github.com/DavidZemon/ParallaxAbstractionLayer
The readme doesn't make it clear where where the file should be placed in the repository, but I'll get that updated today.
Thanks for the proposal, @RossH . I generally like it. The biggest change I would wish for is in the capitalization of function names -- for me that seems to imply a macro or type name rather than a function name. Of course these could be implemented as macros, but still, it just doesn't seem C-like to me. So I'd prefer _pinh, _pinl, etc. to _PINH, _PINL, etc. It's just my preference though, perhaps others feel differently.
Other very minor issues:
(a) Since we're the compiler/library writers, we're allowed to use the "_t" name space, and in fact I read the C99 standard as encouraging this. If we use "polar_t" then it is guaranteed not to conflict with any user variables (assuming the user follows the rules), whereas POLAR doesn't have this guarantee. It's a minor point, since the new header file is Propeller specific anyway, but I guess it could come up if we ever find it useful to include <propeller2.h> in the implementation of some other standard header file.
(b) For the same reason I slightly prefer the C99 type names like "uint32_t" over anything new like ULONG. Actually for anything like locks or cog ids where we don't need to specify the bit width I'd be fine with just "int" or "unsigned int". There's no need to over-specify: a lock, for example, will fit in an 8 bit variable just fine, so I'd be happy to let the compiler use whatever size it wants for this, which is exactly what "int" is for.
(c) There is some merit in using LONG, WORD, BYTE to mimic the way Spin does things. On the other hand we are writing a C header file and users should be familiar enough with the C language to know that "int32_t", "uint16_t", and "uint8_t" are the C names for those things (when the bit sizes matter) or that "int" / "short" / "char" are the names to use when the exact size isn't critical. Again, this'll all come out in the wash so it'll work either way.
Thanks,
Eric
Eric
For example, here's lines 871-876 of arm_neon.h
So let's get back to the question: what if the above version of propeller2.h wasn't included in the compiler? Where else would it go? How would developers get it, and how would they make it available to their build system?
I'd prefer to propose answers to the above questions with the following in mind: keep is easy for the common case, and possible for the rest.
Where would it go?
I'd propose that the contents of propeller2.h (which may not remain all in the same file) be moved to its own Git repository. This repository would be a C-implementation of the Parallax Abstraction Library.
How would developers get it?
Parallax's educational customers would receive a simple all-in-one package - similar to SimpleIDE today - that includes the compiler and Propeller 2 C standard library. They would hardly know the difference between GCC and the Propeller 2 C standard library, just like most of them don't know the difference between SimpleIDE, GCC, and the Simple libraries.
How would "advanced" users add it to their own build system?
CLI users (or anyone who wants to use GCC + P2 outside SimpleIDE2) would download the Propeller 2 C standard library independently of the compiler (preferably in pre-compiled format, from a CI server) and would be writing their own Makefile/CMake script/whatever which adds the library's include path to the compiler's header include search path. Examples could/would/should be provided in the Propeller 2 C standard library README.
This implies (and is confirmed on https://www.gnu.org/software/libc/manual/html_node/Reserved-Names.html) that users should not be using the "_t" naming convention for types, and my above proposal would make the Propeller 2 C standard library just a normal "user" excluding it from the "_t" convention. Not sure what to do about that.
There's the option of ignoring the recommendation, and using _t anyway. Lots of people do it. Alternatively, PascalCase (camelCase with leading capital letter) seems reasonably common. Suffixing with "Type" or "_Type" or "_type" is another... though that seems excessively verbose and unnecessary.
And of course, there is always the SCREAMING_CAPITALS option... though I prefer PascalCase or any of the Type/_Type/_type suffixes to all caps.
I don't agree with point A, because the reason why it won't collide with user defined types it is because "_t" types are system reserved types, normally defined in "stddef.h" and "stdint.h". Users won't typically create variable types ended in "_t" because of this.
Regarding point B, and C, I totally agree. And since we are programming in C, it makes sense to use the C99 naming conventions instead of the ones used for Spin.
Kind regards, Samuel Lourenço