Spin2 intrinsics for P2 functions
ersmith
Posts: 6,053
in Propeller 2
I've started implementing some of the new Spin2 methods for P2 instructions, but I think we should perhaps step back and re-think some of them.
In Spin1, the methods all looked like regular Spin function calls, and if they produced a value it was returned as a result. For example, we would do things like:
The proposed Spin2 intrinsics change this to be more like the PASM instructions:
I don't like the new way of doing things, and I'd like to propose that Spin2 revert to the Spin1 way. I have several objections:
(1) In the old way, it was immediately obvious to the reader (and the compiler) which variables were being changed -- none of the methods modified their parameters, and to update a variable you had to use an assignment operator ending in '='. In the Spin2 proposal that's no longer true; there's nothing to tell the user whether locknew_(x) changes x or not.
(2) It's no longer possible to chain functions together, i.e. to pass results of these intrinsic methods to other methods. This will be particularly painful for the arithmetic functions like GETRND, where the output might reasonably be passed to a user's function.
(3) Global variables like CF_ and ZF_ make reading the program harder (due to hidden state) and inhibit optimizations.
(4) It's generally not compatible with Spin1, or with other languages like C. In C, for example, if we wanted to implement the intrinsics in the same way we'd have to write something like locknew_(&x), since there is no way for a C function to change its parameter. This isn't necessarily a big change, but it will confuse people.
(5) Further to point (4), taking the address of a variable will inhibit optimizations on that variable, because it makes keeping track of the variable's state much harder for the compiler. In general a big part of the work of an optimizing compiler is finding where variables change. That's easier if the variables are only modified by assignment statements.
My overall feeling is that making Spin2 look more like PASM2 kind of defeats the purpose of having a high level language. PASM2 is a very elegant assembly language, and plenty of users will want to write in it -- that's fine. For that matter, I think the "official" Spin2 compiler will probably support inline assembly, so I really don't think we need to implement intrinsics that look just like the PASM2 instructions. I think they should instead fit more comfortably in the high level language.
Any thoughts?
In Spin1, the methods all looked like regular Spin function calls, and if they produced a value it was returned as a result. For example, we would do things like:
x := locknew repeat while lockset(x)
The proposed Spin2 intrinsics change this to be more like the PASM instructions:
locknew_(x) repeat locktry_(x) while cf_ <> 1
I don't like the new way of doing things, and I'd like to propose that Spin2 revert to the Spin1 way. I have several objections:
(1) In the old way, it was immediately obvious to the reader (and the compiler) which variables were being changed -- none of the methods modified their parameters, and to update a variable you had to use an assignment operator ending in '='. In the Spin2 proposal that's no longer true; there's nothing to tell the user whether locknew_(x) changes x or not.
(2) It's no longer possible to chain functions together, i.e. to pass results of these intrinsic methods to other methods. This will be particularly painful for the arithmetic functions like GETRND, where the output might reasonably be passed to a user's function.
(3) Global variables like CF_ and ZF_ make reading the program harder (due to hidden state) and inhibit optimizations.
(4) It's generally not compatible with Spin1, or with other languages like C. In C, for example, if we wanted to implement the intrinsics in the same way we'd have to write something like locknew_(&x), since there is no way for a C function to change its parameter. This isn't necessarily a big change, but it will confuse people.
(5) Further to point (4), taking the address of a variable will inhibit optimizations on that variable, because it makes keeping track of the variable's state much harder for the compiler. In general a big part of the work of an optimizing compiler is finding where variables change. That's easier if the variables are only modified by assignment statements.
My overall feeling is that making Spin2 look more like PASM2 kind of defeats the purpose of having a high level language. PASM2 is a very elegant assembly language, and plenty of users will want to write in it -- that's fine. For that matter, I think the "official" Spin2 compiler will probably support inline assembly, so I really don't think we need to implement intrinsics that look just like the PASM2 instructions. I think they should instead fit more comfortably in the high level language.
Any thoughts?
Comments
There are situations where it is useful, but since Spin2 is supposed to support multiple return values ala RETURN X,Y there is a nice way to solve that problem.
So I fully agree with you. And right now Spin2 is still in the making so nothing is set in stone yet.
And what about global variables when running multiple cogs trying to get a lock?
Mike
That's certainly more cryptic, and less portable (both are negatives) - was it done to try to improve code size ?
Agreed, if you have good in-line ASM, (and the inline asm here is much better than others), it is best to leave the high level language, well, high level....
One question - I've not spotted an example of in-line ASM using a label, but I guess that is valid ?
Well, I can't speak to the "official" Spin2, but in fastspin's dialect labels in in-line ASM are valid. There was a bug in earlier versions of fastspin that sometimes caused a mysterious syntax error when labels were used in ASM blocks, but that should be fixed now.
Example:
I think it's still useful to access many hardware features from the high-level language, so I think we should keep at least some, but I agree that we don't need to duplicate every PASM2 instruction as an intrinsic, and perhaps some should be bundled together (e.g. perhaps setting up smart pins could be a single function that calls wrpin, wxpin, and wypin as appropriate).
You could name it SYSTEM or whatever and even don't need to attach it in the source, if the compiler does not find it in the source it looks into the SYSTEM library.
This would avoid to write X:=SYSTEM.GETRND() one could still write X:=GETRND().
That would allow to use the same library for Spin,Basic and C
just thinking,
Mike
Given fastspin has a pulse and seems to work very well, it's looking more like an "official" Spin2 than anything else ..
Eric
@cgracey did, in the "Parallax Propeller 2 Instructions v32.ods" spreadsheet (the column labelled "Spin Methods"). I'm hoping he will change his mind though, and change everything that reads like "ROR_(Dv, S)" to "D := Ror_(S, S)".
We'll also have to figure out what to do with the CF/ZF "local bits". I'd like to see them returned from the methods where appropriate (as was done with lockset/lockclr in P1). Where both bits are involved we can have multiple return values, so this shouldn't be a problem. Some methods, like "TESTP_", I'd like to see get collapsed into one Spin method, so instead of: we would just have a single that would return 0 or 1. (Or perhaps 0 or -1? Not sure which one would be better.)
Chip suggested it some time back when we were discussing in-line assembly. Said it would simplify the development tools. Also said, the large number of instructions are a factor.
Personally, I feel what ersmith is proposing would be the least painful for people looking to get started on P2. If that makes tool development easy, great!
I am happy either way, but then again, I am not creating toolchains either. Whatever gets us through.
I also remember a discussion about native tools. The idea there was lean and mean, and with HUBEXEC, that could mean a stripped down SPIN, sans nearly all the intrinsics. Frankly, there is room for Chip to go ahead and do that. If it's done as a subset of "full" SPIN, done P1 style, with in-line PASM, everything would largely be OK. Code written with the proposed native dev tools would always work on the larger and more feature rich off chip tools.
this is cool. Since @Chip is still doing hardware you are basically defining the new Spin. And I definitely like the assignment form way better then the assembler form. Because one can use them in expressions.
That brings the question if your compiler could accept both
and
would be another entry in the library, because of the underscore
But I think global flags are just wrong in a multicore environment, especially if you would need a lock on the flag to get a lock...
And this leads to the question if fastspin could support polymorphism(?)
Spin currently does not as far as I know. The question is if your compiler can differentiate between
Because that is something I find very useful in my daily programming work.
Enjoy!
Mike
In Spin2, can we do something about the DIRA / DIRB complication?
As you know, DIRA goes from 0..31, then DIRB the same way.
So let's say you have either a built-in DIR[ ] (or a PDIR[ ], or something).
So i'd be neat if you can go DIR[62] := 1, and have it best optimized case (constant 62, constant 1) compile out to "or dirb, ##$40000000"
Then you're able to, for example, span the gap with DIR[36..30] and have the correct code generated which includes both dira and dirb behind the scenes.
Obviously, any reference to DIRA would still just be DIRA, and DIRA[32] is still invalid.
Just my 2c.
Sure. I still don't like having the parameter x be modified though .
To be fair, I'm pretty sure Chip wasn't proposing that the CF_ and ZF_ flags be global across processors; each processor would have its own copy (they're basically just mirrors of the C and Z flags in the COG).
fastspin does support having default values for function parameters. So if you define myfunction as: then you get
This could be useful for implementing some of the intrinsics where a parameter could be optional.
(The fastspin extensions to Spin are documented in doc/spin.md, which is plain text in "markdown" format.)
Eric
I agree with this, except I think there should be a restriction that any range cannot span across the two registers; the reason is that while the compiler could "do the right thing" if the ranges are constant, if the user does something like DIR[x..y] then generating code that would split the range up at run time would be quite a pain.
(fastspin's BASIC dialect actually has this feature already; you can use "direction", "input", and "output" on P2 to refer to the whole set of 64 pins, with the "don't span a range across bit 32" restriction.)
Intrinsics do NOT need to cover every asm instruction, that's what inline asm is for. Intrinsics should be things that fit the language better, but provide access to hardware features making it possible to do things without inline asm. Sometimes they will end up being one instruction, but they can be multiple, even a great many if it makes sense (like doing I2C read/write/etc.). I think the worst thing would be to make the instrinsics look essentially like the PASM, if you want that, then just make doing single line inline asm possible (e.g. asm( <single asm instruction with params> ).
fastspin certainly has easy inline asm
For contrast, this is an example of gcc in line asm... yes, those quotes and \n\t really are what they expect you to use... this all screams 'go away'.
and this is ASM goto a C source label, I think "jc %l[carry]" is possible from the comment
Maybe ersmith can post fastspin equivalents, which I am sure a far easier to read.
If the code is being compiled for LMM (the default in P1 mode) then the "if_c jmp #carry" will actually be compiled as:
This is nice. would it be possible to enhance that to named parameter on the calling side?
Mike
It should be possible in principle, but we'd need a different syntax; "y := a" is already a valid expression and hence a valid parameter to a function. Perhaps with the "[y]" kind of standing out to indicate that it's a parameter name.
The idea of the "[y] = n" notation is to allow the user to specify the parameters by name, so you could do: and get the same result. This would be more useful if the parameters had more meaningful names, like
But this may end up being more confusing than it's worth.
Google finds Delphi uses the form
Docs.Add(NewTemplate := True);
and that uses the function name to locate the parameter variable.
https://stackoverflow.com/questions/885942/named-optional-parameters-in-delphi
and that also mentions Ruby has named parameters
Why not use the backtick, e.g.
a := MySub(`dir := "W", `spd := 30)
The backslash might also be a good choice, as long as there's no conflict with its use as an abort trap:
a := MySub(\dir := "W", \spd := 30)
A period prefix would also work:
a := MySub(.dir := "W", .spd := 30)
I'm not terribly fond of the [name] syntax, since sometime during Spin's evolution, we might want that notation to refer to a literal array.
-Phil
ser.start(.baudrate := 115200)
where the start method is defined with default values, thus:
PUB start(rxPin = 31, txPin = 30, mode = 0, baudrate = 9600)
-Phil
Other languages can infer that NewTemplate scope belongs in Add, not the outer scope.
That leaves 'assignment is also a legitimate expression'...
Perhaps that can be disabled inside parameter lists ? ie The compiler knows it is expecting a parameter list, & some editors/IDE's will even show the param names/types at this point
-Phil