Shop Learn
New Spin - Page 25 — Parallax Forums

New Spin

1222325272836

Comments

  • JasonDorie wrote: »
    I just read ersmith's example - It'll work, but it requires manually "hooking up" pointers to all the methods of an object you want to call, which seems really cumbersome, given that the compiler should have all the knowledge it needs about what methods are available on an object of a given type.

    I would alter his example to look more like this:

    Generic "hex" object creation:
    TYPE
      FDSTYPE: "fullDuplexSerial"  'this line imports the methods of FullDuplexSerial so the compiler knows what's available, ref's it as FDSTYPE
    
    VAR
      FDSTYPE  fdsInstance   'this line creates a pointer to an object of a defined type
    
    '' Print a hexadecimal number
    PUB hex(value, digits)
      value <<= (8 - digits) << 2
      repeat digits
        fdsInstance.tx(lookupz((value <-= 4) & $F : "0".."9", "A".."F"))  'this line uses the pointer to access a method through the pointer
    
    PUB init( FDSTYPE pointerToOriginal )
      fdsInstance := pointerToOriginal   'this line just copies the pointer passed from the caller to a local var
    

    and to use we'd do something like:
    OBJ
       fds : "FullDuplexSerial"
       t:     "hex.spin"
    
       t.init(@fds)  'push a single pointer to the FDS object
       t.hex($12345, 8)
    

    Ignore for a moment the current implementation limits of Spin - it feels like you keep offloading things the compiler can do onto the user because you're stuck in how the Spin compiler currently works.
    That looks good except that it doesn't deal with the fact that you might have a number of different I/O object types that you'd like to use with your "hex.spin" object. That is where interfaces come in. Each of those I/O object types would have to implement the same interface and the "hex.spin" object would take an argument of the interface type not the type of one of the I/O objects. That means it can be used generically with any object that implements the I/O interface.
  • cgracey wrote: »
    >> i8 / u8
    >> i16 / u16
    >> i32 / u32
    >> i64 / u64

    Something like that would be very clean.

    That's how I produce a lot of my own code in C and C++. I define these types to map to the built-in char/int/long/etc, however they map. It makes it very explicit when I'm using types that care about size/sign, and it's a whole lot less typing than 'unsigned char'. :)
  • JasonDorie wrote: »
    cgracey wrote: »
    >> i8 / u8
    >> i16 / u16
    >> i32 / u32
    >> i64 / u64

    Something like that would be very clean.

    That's how I produce a lot of my own code in C and C++. I define these types to map to the built-in char/int/long/etc, however they map. It makes it very explicit when I'm using types that care about size/sign, and it's a whole lot less typing than 'unsigned char'. :)
    If you're programming in C, why not just use stdint.h?

  • David Betz wrote: »
    That looks good except that it doesn't deal with the fact that you might have a number of different I/O object types that you'd like to use with your "hex.spin" object. That is where interfaces come in. Each of those I/O object types would have to implement the same interface and the "hex.spin" object would take an argument of the interface type not the type of one of the I/O objects. That means it can be used generically with any object that implements the I/O interface.

    I don't see how that'd differ in that case. If the imported definition was an interface instead of an object, and anything you passed to the user could be an implementer of that interface, you're basically done:
    TYPE
      XMTYPE: "XmitInterface"  'this line imports the methods of XmitInterface so the compiler knows what's available, ref's it as XMTYPE
    
    VAR
      XMTYPE  xmInstance   'this line creates a pointer to an object of a defined type
    
    '' Print a hexadecimal number
    PUB hex(value, digits)
      value <<= (8 - digits) << 2
      repeat digits
        xmInstance.tx(lookupz((value <-= 4) & $F : "0".."9", "A".."F"))  'this line uses the pointer to access a method through the pointer
    
    PUB init( XMTYPE pointerToOriginal )
      xmInstance := pointerToOriginal   'this line just copies the pointer passed from the caller to a local var
    

    The generated code from the back end would change a little bit to call through the vtable instead of just being a direct link to the function. Chip - I get the impression you've never looked at how C++ handles classes, even without interfaces. Doing a bit of reading on that might help you. How they've solved it is reasonably elegant and has less overhead than Spin.
  • David Betz wrote: »
    If you're programming in C, why not just use stdint.h?

    Because typing an underscore requires using the shift key, and they don't have a single entry without one. They're lengthy and cumbersome. int8_t is 7 keystrokes if you include the (shift) required for the underscore. i8 is two, and isn't littered with the weird _t postfix.
  • JasonDorie wrote: »
    David Betz wrote: »
    That looks good except that it doesn't deal with the fact that you might have a number of different I/O object types that you'd like to use with your "hex.spin" object. That is where interfaces come in. Each of those I/O object types would have to implement the same interface and the "hex.spin" object would take an argument of the interface type not the type of one of the I/O objects. That means it can be used generically with any object that implements the I/O interface.

    I don't see how that'd differ in that case. If the imported definition was an interface instead of an object, and anything you passed to the user could be an implementer of that interface, you're basically done:
    TYPE
      XMTYPE: "XmitInterface"  'this line imports the methods of XmitInterface so the compiler knows what's available, ref's it as XMTYPE
    
    VAR
      XMTYPE  xmInstance   'this line creates a pointer to an object of a defined type
    
    '' Print a hexadecimal number
    PUB hex(value, digits)
      value <<= (8 - digits) << 2
      repeat digits
        xmInstance.tx(lookupz((value <-= 4) & $F : "0".."9", "A".."F"))  'this line uses the pointer to access a method through the pointer
    
    PUB init( XMTYPE pointerToOriginal )
      xmInstance := pointerToOriginal   'this line just copies the pointer passed from the caller to a local var
    

    The generated code from the back end would change a little bit to call through the vtable instead of just being a direct link to the function. Chip - I get the impression you've never looked at how C++ handles classes, even without interfaces. Doing a bit of reading on that might help you. How they've solved it is reasonably elegant and has less overhead than Spin.
    I guess the underlying changes are what I was talking about. There needs to be a way for an object definition to say that it implements a specified interface. And, as Eric pointed out, we need to decide if we want to support objects that implement multiple interfaces. That is significantly more complex.
  • David Betz wrote: »
    I guess the underlying changes are what I was talking about. There needs to be a way for an object definition to say that it implements a specified interface. And, as Eric pointed out, we need to decide if we want to support objects that implement multiple interfaces. That is significantly more complex.

    I think there's value in that, but I'd be happy with even the simple case. I can't think of a single instance where I've *needed* a virtual interface in a Prop project. If you're at that level you might as well just use C++. I *can* however think of numerous cases where the fact that I can't use the same FullDuplexSerial or Float32 objects easily in multiple sub-objects was a pain in the ****.
  • JasonDorie wrote: »
    David Betz wrote: »
    I guess the underlying changes are what I was talking about. There needs to be a way for an object definition to say that it implements a specified interface. And, as Eric pointed out, we need to decide if we want to support objects that implement multiple interfaces. That is significantly more complex.

    I think there's value in that, but I'd be happy with even the simple case. I can't think of a single instance where I've *needed* a virtual interface in a Prop project. If you're at that level you might as well just use C++. I *can* however think of numerous cases where the fact that I can't use the same FullDuplexSerial or Float32 objects easily in multiple sub-objects was a pain in the ****.
    What about the example you just mentioned? Using a generic "hex.spin" object with FDS, VGA, or LCD drivers. That would require the interface feature.
  • As I said, I think there's value in it but I'd be happy with the simple case. What you're suggesting could be handled by a simple function pointer in C, though I guess Spin doesn't have those, so ...

    Honestly, if the P2 has C++ support I'm unlikely to ever touch Spin unless I have to. It's great for simple things, but too cumbersome for anything complex, and I worry that asking for support of this scope in Spin2 is just going to derail the P2 even further. Given that P2's interpreter will be loaded along with the code, it feels like a discussion we could easily defer until after we have silicon in hand.
  • JasonDorie,
    I think with the larger memory size and double the number of COGs with double the amount of memory in each of those, programs will be more complex, and Making Spin less cumbersome will benefit all the people that would rather use it than C++.

    You and I will use C++, because we live in it for our day jobs. Many will not.

    David Betz, ersmith,
    I really don't think having a single object implement multiple interfaces is significantly more complex. Yes, it adds a little bit more work for the compiler, but that's fine.
  • ersmithersmith Posts: 5,016
    edited 2017-03-01 02:36
    Roy Eltham wrote: »
    David Betz, ersmith,
    I really don't think having a single object implement multiple interfaces is significantly more complex. Yes, it adds a little bit more work for the compiler, but that's fine.

    It's not conceptually difficult, but in practice the complexity is in figuring out which of the multiple interfaces of an object to pass as a parameter. Or, alternatively, finding the right interface in an arbitrary object (which may have multiple, differing interfaces) at run time. Both problems are certainly solvable, but at the cost of language complexity and/or runtime speed, I think.

    But I'm willing to be proven wrong, if someone can provide a concrete example.

    Eric
  • ersmith wrote: »
    Roy Eltham wrote: »
    David Betz, ersmith,
    I really don't think having a single object implement multiple interfaces is significantly more complex. Yes, it adds a little bit more work for the compiler, but that's fine.

    It's not inherently more complex -- the complexity is in figuring out which of the multiple interfaces of an object to pass as a parameter. Or, alternatively, finding the right interface in an arbitrary object (which may have multiple, differing interfaces) at run time. Both problems are certainly solvable, but at the cost of language complexity and/or runtime speed, I think.

    But I'm willing to be proven wrong, if someone can provide a concrete example.

    Eric
    Of course, this has already been solved by C++. We can just look at how they did it.
  • JasonDorie wrote: »
    As I said, I think there's value in it but I'd be happy with the simple case. What you're suggesting could be handled by a simple function pointer in C, though I guess Spin doesn't have those, so ...
    As David has pointed out, your modified version of my example only works for FullDuplexSerial objects, and we'd really like the hex method to work for any outputter (VGA, Serial, NTSC, etc.). Supporting objects with a very simple inheritence heirarchy is straightforward, if every object starts with a vtable and they all have compatible layouts. But moving away from that quickly gets you into more complicated situations.

    Function pointers are I think the right answer, and that's what I tried to provide with "method pointers". Just as in C++, Spin method pointers are going to be more complicated than C function pointers, but I don't think it's completely unwieldy. And they solve the general case with minimal changes to the language. I used to be in favor of adding interfaces and object pointers, with the compiler figuring out the types as necessary, but I think method pointers will make that largely unnecessary. (Simple object pointers with a known type should still go in, since they're so easy to implement.)

    Eric
  • C++ and most OO, it is compile time. That is where name mangling comes to play.

    In most OO you just have the normal procedural language, and a hidden parameter to each function is a pointer to the struct that is the definition of the class (the this pointer), the name mangling is used to encode the parameter types for the linker so that multiple linkable object files can mesh.

    It is about as simple as you can get. Same goes for overloading parameters, just sepperate functions compiled with different names, even though the names appear identacle in the source, the name mangling adds the parameter types in strange ways.
  • ersmith wrote: »
    JasonDorie wrote: »
    As I said, I think there's value in it but I'd be happy with the simple case. What you're suggesting could be handled by a simple function pointer in C, though I guess Spin doesn't have those, so ...
    As David has pointed out, your modified version of my example only works for FullDuplexSerial objects, and we'd really like the hex method to work for any outputter (VGA, Serial, NTSC, etc.). Supporting objects with a very simple inheritence heirarchy is straightforward, if every object starts with a vtable and they all have compatible layouts. But moving away from that quickly gets you into more complicated situations.

    Function pointers are I think the right answer, and that's what I tried to provide with "method pointers". Just as in C++, Spin method pointers are going to be more complicated than C function pointers, but I don't think it's completely unwieldy. And they solve the general case with minimal changes to the language. I used to be in favor of adding interfaces and object pointers, with the compiler figuring out the types as necessary, but I think method pointers will make that largely unnecessary. (Simple object pointers with a known type should still go in, since they're so easy to implement.)

    Eric
    I guess we disagree on this. I think method pointers are too cumbersome in all but the simplest cases.

  • JasonDorieJasonDorie Posts: 1,930
    edited 2017-03-01 07:49
    Yeah, I agree with David here - If the "method pointers" idea is what you were suggesting in your original hex / fds example, it's cumbersome. Having FDS, VGA, TV, etc all implement a "text output" interface, and passing that interface to the hex function would be much cleaner.
  • JasonDorie wrote: »
    Honestly, if the P2 has C++ support I'm unlikely to ever touch Spin unless I have to. It's great for simple things, but too cumbersome for anything complex, and I worry that asking for support of this scope in Spin2 is just going to derail the P2 even further. Given that P2's interpreter will be loaded along with the code, it feels like a discussion we could easily defer until after we have silicon in hand.

    I think the Spin effort is largely to make sure the P2 instruction set will efficiently support HLLs. The conversation had already produced a few new instructions. Hopefully, work is also underway to get C/C++ compiling in it as well. Though not exhaustive, having a working compiled language and a working interpreted language should give us confidence about the P2 really being ready for prime time.
  • JasonDorie wrote: »
    Yeah, I agree with David here - If the "method pointers" idea is what you were suggesting in you original hex / fds example, it's cumbersome. Having FDS, VGA, TV, etc all implement a "text output" interface, and passing that interface to the hex function would be much cleaner.
    Making it possible for an object to implement a single interface should be quite easy to do. And Roy says he has a way to make multiple interfaces possible as well. Why not go with this simpler approach?

  • I don't have a way to make multiple interfaces work yet, I just think it would not be that difficult.
    The exact details of how it would work depends on a bunch of other things that are still being decided.

    However, I think the key will be when you pass an implementer object into a method that takes an interface type, something will be calculated then to get the specific interface methods out of the implementer object. Essentially a dynamic cast operation. It boils down to an offset to the interface methods in the table for the object that needs to be used for calls in the method using the interface.
  • Cluso99Cluso99 Posts: 17,946
    What I think would be nice is a P1 SPIN compatible interpreter. While counters and a few other things are not available, there otherwise is no major reason it would not work. Spin1 supports 64KB HUB RAM, so we can write spin programs double the size.

    Many P1 PASM instructions will just compile too.

    So I see a lot of advantages for just a P1 compatible. I might just do it ;)

    This in no way detracts from SPIN2. It will just be another tool.
  • Seairth wrote: »
    I think the Spin effort is largely to make sure the P2 instruction set will efficiently support HLLs.

    That's an interesting observation but I wonder if it's true?

    Over the years I've noticed that compilers don't use the full instruction set of the host micro, instead instruction use tends to follow the 10:90 rule with 90% of the resulting binary output using just 10% of the instructions available. I've also noticed that there are often instructions that just don't get used. I've lost count of how many different instructions the P2 will have but how many will actually get used?

    We can discount what Spin will do as that's self-serving but what about C and C++, what about Javascript and Python?

    If I had to guess I'd say that those 4 languages will end up being used to program over 90% of P2s sold, maybe more.

    Given that those languages are currently being used very effectively on a range of chips that don't have such complex instruction sets I can't help feeling that a lot of bright people are expending a lot of time and energy on a pointless exercise. It is, of course, their absolute right to do this but in the meantime the P2 shipping date slips further into the distance.
  • cgraceycgracey Posts: 13,610
    JasonDorie wrote: »
    cgracey wrote: »
    Question, is there any realistic need for thousands of instances of a particular object? Might even 256 be excessive?

    2D points in a paint program. 3D vectors in an IMU, ... 640 should be enough for everyone. :) Roy's suggestion of setup on startup is pretty common. The "constructor" of an object (code called when you create one) fills in the necessary pointers after the memory is cleared, calls any nested constructors in objects it might own.

    I get it now.
  • cgraceycgracey Posts: 13,610
    There are lots of instructions so you can have fun programming in PASM.
  • David, Jason, Roy:

    You may be right about method pointers becoming cumbersome for large interfaces; I was thinking in terms of things like text input and output with just a few methods, but a graphics interface might have a lot more.

    In that case, I think the best option would be to introduce (optional) type declarations for function parameters. That way the compiler can figure out what type of interface to pass when given a multi interface object. If there's no type declaration on a parameter then it remains a generic Bit32, as now. Having type declarations would also make supporting floating point easier.

    I guess an alternative would be to require the programmer to explicitly specify the interface at the function call spot, like hex(@fds.output, x) rather than hex(fds, x) That seems risky given that if the programmer forgets to specify then things could really go into the weeds.
  • cgracey wrote: »
    There are lots of instructions so you can have fun programming in PASM.

    For hobbyists that might be true but nobody programs in assembler if they want to be productive and write maintainable code. For educational use Spin is a dead-end; there are no jobs advertised for Spin programmers. If education is a target market that some sort of GUI based drag-and-drop language will be accepted but that will hide the P2 and the processor under all that could be anything. The market is full of processors at $2 each which will steer a robot around a course.

    90% of potential P2 sales will be to people who want to program it in C or C++.
  • Roy ElthamRoy Eltham Posts: 2,994
    edited 2017-03-01 09:34
    ersmith,
    I hope we can do it without having to specify types on params. I think it's enough to type it at usage like Spin does now, and just have the compiler do a little extra work to make it not cost too much at runtime to get to the proper method.
    I think we need to see what Chip does with what he says he "gets" now and morph that to something better via iteration.

    Brian Fairchild,
    For testing things, we have a bunch of forum users and enthusiasts with FPGA boards. Those guys (myself included) will happily do PASM to test things. They already have been. Getting Spin up and running will help that effort, since we can build some PASM drivers and feed/control/mix them with Spin. It will help test all the nooks and crannies of the design on FPGAs as much as possible. Then we'll have all that test code to use when testing the actual silicon. it seems like a reasonable plan Chip has going. ersmith also has his fastspin that produces PASM from Spin, that can be used as well, I'm thinking (hoping) we can incorporate that into the new version of OpenSpin for P2 as the mechanism to optionally compile Spin2 to PASM.

    Also, I'm hoping the propgcc guys are willing to help get at least a first pass variant going on P2.
  • Brian FairchildBrian Fairchild Posts: 537
    edited 2017-03-01 10:17
    Roy Eltham wrote: »
    ersmith,
    Getting Spin up and running will help that effort, since we can build some PASM drivers and feed/control/mix them with Spin.

    But there's getting Spin up and running, and then there's endless discussion about the most elegant way to pass information back and forth. At the end of the day, data will be put somewhere by one cog and another cog will read that data. For testing the chip, that doesn't need the level of complexity that is being discussed. For testing the chip, all you need is a way to pass a 'pointer' to a block of RAM from one cog to another.
  • Roy Eltham wrote: »
    ersmith,
    I hope we can do it without having to specify types on params. I think it's enough to type it at usage like Spin does now, and just have the compiler do a little extra work to make it not cost too much at runtime to get to the proper method.
    I think we need to see what Chip does with what he says he "gets" now and morph that to something better via iteration.
    I keep going back and forth on this myself. On the one hand I like not having to declare variable types. On the other hand it really will be easier for the compiler if the user does this, and moreover it might end up being less typing. Consider something like:
    OBJ
       OutputInterface = "OutputTerminal"
    
    PUB printdata(port, a, b)
       OutputInterface[port].str("The values are: ")
       OutputInterface[port].dec(a)
       OutputInterface[port].str(" and ")
       OutputInterface[port].dec(b)
    
    as opposed to
    PUB printdata(port = "OutputTerminal", a, b)
       port.str("The values are: ")
       port.dec(a)
       port.str(" and ")
       port.dec(b)
    
    The second version is much clearer, I think. We could still have non-object parameters default to plain LONG as we do now.
  • How complex do we want spin to become? Interfaces and/or inheritance is nice but they make the language more complex to understand.

    Making spin more dynamic and adding a heap will invite memory management bugs. At the moment they (memory management bugs) can't happen.

    If we don't have interfaces or inheritance, pbase can be calculated at compile time and only vbase needs to be passed aroundso only a 32bit pointer would be needed. A simple form of inheritance could probably be made to work with this.

    Interfaces are more difficult, it is likely that two levels of lookup will be required rather than the current single level.

    It is actually possible to load objects at runtime in spin1 if you don't mind messing with the object table at runtime. I did it several years back, loading precomputed objects from an Sd card.

    If we add a heap we are going to need to add some kind allocate and free keywords.

    When I did it I used a syntax like this
    OBJ
      TEST : #"test" 'leaves a blank spot in the object table for an object with type test. This only works if there is only a single instance of the calling object in use.
        
    PUB main(objAddress) 'objAddress is the address of the object you want to call, I had to do some messing around because spin1 expects to have relative addresses. For spin2 it should be easy enough to use an absolute address instead.
      
      'to use it we just assign the address to the object
      TEST=objAddress
    
      'and then just call a method in the object like normal
      TEST.someMethod(123)
    
  • How complex do we want spin to become? Interfaces and/or inheritance is nice but they make the language more complex to understand.

    Making spin more dynamic and adding a heap will invite memory management bugs. At the moment they (memory management bugs) can't happen.

    If we don't have interfaces or inheritance, pbase can be calculated at compile time and only vbase needs to be passed aroundso only a 32bit pointer would be needed. A simple form of inheritance could probably be made to work with this.

    Interfaces are more difficult, it is likely that two levels of lookup will be required rather than the current single level.

    It is actually possible to load objects at runtime in spin1 if you don't mind messing with the object table at runtime. I did it several years back, loading precomputed objects from an Sd card.

    If we add a heap we are going to need to add some kind allocate and free keywords.

    When I did it I used a syntax like this
    OBJ
      TEST : #"test" 'leaves a blank spot in the object table for an object with type test. This only works if there is only a single instance of the calling object in use.
        
    PUB main(objAddress) 'objAddress is the address of the object you want to call, I had to do some messing around because spin1 expects to have relative addresses. For spin2 it should be easy enough to use an absolute address instead.
      
      'to use it we just assign the address to the object
      TEST=objAddress
    
      'and then just call a method in the object like normal
      TEST.someMethod(123)
    
    Are you saying that the Propeller Tool will compile this code? I didn't know that assignments to object variables was allowed. Also, shouldn't it be "TEST:=objAddress"?

Sign In or Register to comment.