Shop OBEX P1 Docs P2 Docs Learn Events
Spin2 intrinsics for P2 functions — Parallax Forums

Spin2 intrinsics for P2 functions

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:
  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?
«1

Comments

  • RaymanRayman Posts: 13,797
    can we just get rid of intrinsics and use inline assembly only?
  • msrobotsmsrobots Posts: 3,701
    edited 2019-01-06 21:38
    Modification of parameters (aka by reference instead of by value) is in my opinion to be avoided in any language as much as possible.

    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
  • jmgjmg Posts: 15,140
    ersmith wrote: »
    (3) Global variables like CF_ and ZF_ make reading the program harder (due to hidden state) and inhibit optimizations.

    That's certainly more cryptic, and less portable (both are negatives) - was it done to try to improve code size ?

    ersmith wrote: »
    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?

    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 ?

  • jmg wrote: »
    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:
    con
      _clkmode = xtal1 + pll16x
      _clkfreq = 80_000_000
    
    obj
      ser: "FullDuplexSerial"
    
    pub demo
      ser.start(31, 30, 0, 115200)
      labeltest(0)
      labeltest(1)
      labeltest(2)
      
    pub labeltest(arg) | x
      x := 0
      ser.str(string("labeltest("))
      ser.dec(arg)
      ser.str(string("): x="))
      asm
        cmp arg, #1 wz
     if_nz jmp skip
        mov x, 1
    skip
      endasm
      ser.dec(x)
      ser.tx(13)
      ser.tx(10)
    
  • Rayman wrote: »
    can we just get rid of intrinsics and use inline assembly only?

    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).
  • msrobotsmsrobots Posts: 3,701
    edited 2019-01-06 22:21
    I never wrote a compiler so I am not sure how much work this would be to optimize this, but shouldn't stuff like that end up in some library file one could attach as class/object/include?
    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
  • jmgjmg Posts: 15,140
    ersmith wrote: »
    ... 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.
    Cool, great, pretty much what I hoped/expected :)
    ersmith wrote: »
    Well, I can't speak to the "official" Spin2...
    Given fastspin has a pulse and seems to work very well, it's looking more like an "official" Spin2 than anything else .. :)

  • Did I miss something? Who proposed the "new way" of doing the P2 intrinsics? Seems pretty awful to me. Keep the old way.
  • msrobots wrote: »
    I never wrote a compiler so I am not sure how much work this would be to optimize this, but shouldn't stuff like that end up in some library file one could attach as class/object/include?
    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.
    That's very much like how fastspin actually does implement a lot of the builtin functions. For example, locknew is defined (for P1) as:
    pri locknew | rval
      asm
        locknew rval
      endasm
      return rval
    
    That's one of the reasons I hope Chip will agree to change the Spin2 builtin methods, I'd like to be able to keep doing that for the new builtins.

    Eric
  • Roy Eltham wrote: »
    Did I miss something? Who proposed the "new way" of doing the P2 intrinsics? Seems pretty awful to me. Keep the old way.

    @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:
      TESTP_(D, WC/WZ)
      TESTP_(D, ANDC / ANDZ)
      TESTP_(D, XORC / XORZ)
      ... etc ..
    
    we would just have a single
      x := TESTPIN_(Y)
    
    that would return 0 or 1. (Or perhaps 0 or -1? Not sure which one would be better.)
  • potatoheadpotatohead Posts: 10,253
    edited 2019-01-07 01:02
    Roy Eltham wrote: »
    Did I miss something? Who proposed the "new way" of doing the P2 intrinsics? Seems pretty awful to me. Keep the old way.

    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.

  • ersmith wrote: »
    msrobots wrote: »
    I never wrote a compiler so I am not sure how much work this would be to optimize this, but shouldn't stuff like that end up in some library file one could attach as class/object/include?
    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.
    That's very much like how fastspin actually does implement a lot of the builtin functions. For example, locknew is defined (for P1) as:
    pri locknew | rval
      asm
        locknew rval
      endasm
      return rval
    
    That's one of the reasons I hope Chip will agree to change the Spin2 builtin methods, I'd like to be able to keep doing that for the new builtins.

    Eric

    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
    x := locknew
    
    and
    locknew_(x)
    

    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
    x:=myfunction
    X:=myfunction(a)
    X:=myfunction(a,b)
    

    Because that is something I find very useful in my daily programming work.

    Enjoy!

    Mike


  • Speaking of built-in functions,
    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.
  • I completely agree in keeping things as close to Spin1 as possible. Small changes to syntax are going to create an unnecessary learning curve, at least for me. As far as being able to overload a method, I'm really not sure how I feel about this. I personally think Spin2 should be kept very simple and not try to add too many things other languages do perfectly well. Inline ASM should be the goto for high performance and the less that's added or changed, the easier it's going to be for people comfortable with Spin1 to get programming with spin2 quickly.

    Just my 2c.
  • msrobots wrote: »
    That brings the question if your compiler could accept both
    x := locknew
    
    and
    locknew_(x)
    

    would be another entry in the library, because of the underscore

    Sure. I still don't like having the parameter x be modified though :(.
    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...
    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).
    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
    x:=myfunction
    X:=myfunction(a)
    X:=myfunction(a,b)
    

    fastspin does support having default values for function parameters. So if you define myfunction as:
    pub myfunction(x = 99, y = 100)
    ...
    
    then you get
    x := myfunction  ' same as x := myfunction(99, 100)
    x := myfunction(a) ' same as x := myfunction(a, 100)
    x := myfunction(a, b)
    

    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
  • whicker wrote: »
    Speaking of built-in functions,
    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.

    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.)
  • Hmmm... I hope Chip can be convinced to do something better than what he proposed. Ugh!

    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> ).

  • I am not sure that was ever brought up Roy. I agree.
  • cgraceycgracey Posts: 14,133
    Man, I should have read this thread earlier. These Spin2 intrinsics are a red herring issue. It was just kind of an exercise in seeing how PASM instructions could be represented in Spin2. I agree that this would be much better addressed by easy inline assembly. Imagine you never saw them.
  • jmgjmg Posts: 15,140
    edited 2019-01-07 20:01
    cgracey wrote: »
    Man, I should have read this thread earlier. These Spin2 intrinsics are a red herring issue. It was just kind of an exercise in seeing how PASM instructions could be represented in Spin2. I agree that this would be much better addressed by easy inline assembly. Imagine you never saw them.

    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'.
    int src = 1;
    int dst;   
    
    asm ("mov %1, %0\n\t"
        "add $1, %0"
        : "=r" (dst) 
        : "r" (src));
    
    printf("%d\n", dst);
    This code copies src to dst and add 1 to dst.
    

    and this is ASM goto a C source label, I think "jc %l[carry]" is possible from the comment
    Alternately, you can reference labels using the actual C label name enclosed in brackets. For example, to reference a label named carry, you can use ‘%l[carry]’. 
    The label must still be listed in the GotoLabels section when using this approach.
    
    Here is an example of asm goto for i386:
    
    asm goto (
        "btl %1, %0\n\t"
        "jc %l2"
        : /* No outputs. */
        : "r" (p1), "r" (p2) 
        : "cc" 
        : carry);
    
    return 0;
    
    carry:
    return 1;
    


    Maybe ersmith can post fastspin equivalents, which I am sure a far easier to read.
  • fastspin inline asm (the C variant):
    // copy src to dst and add one to it
    // (src and dst must be local variables)
    __asm {
      mov dst, src
      add dst, #1
    }
    ...
    // function to return 0 if p1 & p2 has even parity, 1 if not
    int testparity(int p1, int p2)
    {
      // jump to code carry if p1 < p2
      __asm {
        test p1, p2 wc
        if_c jmp #carry
      }
      return 0;
    carry:
      return 1;
    }
    

    If the code is being compiled for LMM (the default in P1 mode) then the "if_c jmp #carry" will actually be compiled as:
       if_c rdlong pc, pc
           long @ @ @ carry
    

  • msrobotsmsrobots Posts: 3,701
    edited 2019-01-08 03:54
    ersmith wrote: »
    msrobots wrote: »
    That brings the question if your compiler could accept both
    x := locknew
    
    and
    locknew_(x)
    

    would be another entry in the library, because of the underscore

    Sure. I still don't like having the parameter x be modified though :(.
    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...
    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).
    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
    x:=myfunction
    X:=myfunction(a)
    X:=myfunction(a,b)
    

    fastspin does support having default values for function parameters. So if you define myfunction as:
    pub myfunction(x = 99, y = 100)
    ...
    
    then you get
    x := myfunction  ' same as x := myfunction(99, 100)
    x := myfunction(a) ' same as x := myfunction(a, 100)
    x := myfunction(a, b)
    

    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

    This is nice. would it be possible to enhance that to named parameter on the calling side?
    x := myfunction  ' same as x := myfunction(99, 100)
    x := myfunction(a) ' same as x := myfunction(a, 100)
    
    x := myfunction(y := a) ' same as x := myfunction(99, a)
    
    x := myfunction(a, b)
    

    Mike
  • msrobots wrote: »
    This is nice. would it be possible to enhance that to named parameter on the calling side?
    x := myfunction  ' same as x := myfunction(99, 100)
    x := myfunction(a) ' same as x := myfunction(a, 100)
    
    x := myfunction(y := a) ' same as x := myfunction(99, a)
    
    x := myfunction(a, b)
    

    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
    myfunction( [y] = a )
    
    with the "[y]" kind of standing out to indicate that it's a parameter name.
  • An alternative could be using a placeholder symbol, maybe?
    myfunction( *, a )
    
  • Roy Eltham wrote: »
    An alternative could be using a placeholder symbol, maybe?
    myfunction( *, a )
    

    The idea of the "[y] = n" notation is to allow the user to specify the parameters by name, so you could do:
    myfunction( [y] = b, [x] = a )
    myfunction( [x] = a, [y] = b )
    
    and get the same result. This would be more useful if the parameters had more meaningful names, like
    openfile( [name] = "hello.txt", [mode] = READ_ONLY, [disk] = sdcard)
    

    But this may end up being more confusing than it's worth.
  • jmgjmg Posts: 15,140
    ersmith wrote: »
    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

    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

  • Phil Pilgrim (PhiPi)Phil Pilgrim (PhiPi) Posts: 23,514
    edited 2019-01-08 20:09
    jmg wrote:
    Docs.Add(NewTemplate := True);
    That would be ambiguous in Spin, since NewTemplate might also be a variable in the calling program, and an assignment is also a legitimate expression.

    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
  • I really like the idea of calling with named parameters, BTW. Think how much easier a call to FullDuplexSerial's start method could be, for example:

    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
  • jmgjmg Posts: 15,140
    jmg wrote:
    Docs.Add(NewTemplate := True);
    That would be ambiguous in Spin, since NewTemplate might also be a variable in the calling program, and an assignment is also a legitimate expression.
    Prefix chars look like a kludge.
    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 Pilgrim (PhiPi)Phil Pilgrim (PhiPi) Posts: 23,514
    edited 2019-01-08 21:48
    jmg wrote:
    Prefix chars look like a kludge.
    Not really. (I suppose you'd say the same about @ in Spin, and the use of \ in Perl to denote a reference?) Especially in the case of the period, it's an elegant way to designate an element of an object or method when the object or method itself is assumed.

    -Phil
Sign In or Register to comment.