Shop OBEX P1 Docs P2 Docs Learn Events
New Spin - Page 23 — Parallax Forums

New Spin

1202123252636

Comments

  • but in parental VAR space, there might be some impetus to recycle memory.

    They could plan for this and reuse memory.
  • I think it's simpler to just let the user/coder manage the instances they dynamically create that are not on the stack as locals and they can reuse the memory as needed.
  • cgraceycgracey Posts: 14,206
    Spin would need access to the VAR size, which is simple.
  • cgracey wrote: »
    Roy Eltham wrote: »
    Heaps can work several different ways. Typically you have a block of memory that is the heap, and it's managed as a set of allocated blocks and a linked list of free blocks. Initially you have a single free block entry that is all of the memory, then as you allocate you find a large enough free block and divide the free block into two pieces one for the allocation and the other to stay in the free list (or just use the whole block for the allocation if it's fits. Later, If you free a block you add it to the free list. You can do some extra work at free time to combine consecutive free blocks.

    There are other more advanced schemes that try to reduce fragmentation, etc. I'm not sure we need a heap for Spin. We can just have global scope (in VARs) object instances all be pre allocated. And dynamic ones can be either locals in the stack, or the user can provide a buffer space for it (like using a byte array VAR space to hold dynamic instances of a given object type).

    Makes sense. I really like the idea of dynamically created and destroyed objects. You could even have a heap for OBJects, couldn't you? Maybe that's unnecessary, but it would allow for really big programs, where OBJects get pulled in from memory, as needed.

    If you want to go this route, then you will want objects to have a predefined constructor and destructer methods (they can be empty), do allow each instance to set itself up and clean itself up. The memory allocation mechanism is kind of independent of that, and could be stack, heap, or array.

    If by "heap of objects" you mean having a bunch of object definitions (code,etc.) in the flash that can be loaded in and created dynamically, then yes.
  • cgraceycgracey Posts: 14,206
    edited 2017-02-28 07:32
    There is one thing I'm not sure about, and that is, I think we need a pointer to not just an object, but to an object and a particular method within it, in compound, so that we can dynamically vector calls using a generic construct.

    For example, say you have that number-format-and-output object that needs to direct its character output to some method that needs to be changeable. There is no single 'type' to compile the code by. You might only need to pass one parameter, anyway. So, you need a pointer that can be redirected to various 'type's at runtime.

    Is this sensible, or am I just missing some concept that the main paradigm already affords?
  • Phil Pilgrim (PhiPi)Phil Pilgrim (PhiPi) Posts: 23,514
    edited 2017-02-28 07:53
    Many moons ago, I wrote a heap manager in Spin:

    http://forums.parallax.com/discussion/85772/dynamic-strings-heap-manager-formatted-output

    It uses the "first-fit" protocol and allows space to be freed when no longer needed. When freed, the space is merged with adjacent memory and melded back into the "free" list. Maybe it could be a model for dynamic object allocation in Spin2; maybe not.

    -Phil
  • In C++, if you have a pointer to a member function of an object, you can't use it by itself. you need a pointer to an object to use with it. The syntax is kind of odd, and rarely used (at least in the circles of coders I know and work with).

    If you have a pointer to the object, then you can call any method in it. That accomplishes the main goal.

    Now within an object, it would be handy to be able to call methods via an indirection. As in a table of functions that you can call based on data or user input or whatever. It's a nice way to clean up long if/else chains or large case constructs, but it also allows you to reconfigure the array of functions and change things. This is kind of an advanced feature thing, so maybe not something for normal Spin?
  • cgraceycgracey Posts: 14,206
    edited 2017-02-28 08:31
    Roy Eltham wrote: »
    In C++, if you have a pointer to a member function of an object, you can't use it by itself. you need a pointer to an object to use with it. The syntax is kind of odd, and rarely used (at least in the circles of coders I know and work with).

    If you have a pointer to the object, then you can call any method in it. That accomplishes the main goal.

    Now within an object, it would be handy to be able to call methods via an indirection. As in a table of functions that you can call based on data or user input or whatever. It's a nice way to clean up long if/else chains or large case constructs, but it also allows you to reconfigure the array of functions and change things. This is kind of an advanced feature thing, so maybe not something for normal Spin?

    I think C++ insists on doing it that way so that it can perform branch analysis correctly for optimization.

    There still needs to be a way to call methods, indirectly, at run-time, that are not certain at compile-time. Like in a number-printing object, you have to be able to give it a pointer to a method which receives its character output and does something with it. It doesn't know anything about that method at compile time. I'm not getting how you bridge the gap.
  • cgracey wrote: »
    So that's 8 bits in a BYTE, 32 bits in a WORD, 64 bits in a LONG, and 128 bits in an OBJ?

    What do people use for 16 bits?

    BYTE = 8 bits
    WORD = 16 bits
    LONG = 32 bits
    OBJ = was going to be 64 bits (needs another name to not trigger OBJ block)

    But that breaks decades of convention where WORD is the native data size of the CPU.
  • Brian FairchildBrian Fairchild Posts: 549
    edited 2017-02-28 09:09
    A few words that have been used to describe variables in other languages.

    From PL/M80, which ran on 8-bit CPUs with a 64k address space...
    BYTE - 8-bit unsigned value
    ADDRESS - 16-bit unsigned value

    From PL/M86, which ran on 16-bit CPUs...
    BYTE, as above
    WORD - 16-bit unsigned value
    DWORD - 32-bit unsigned value
    INTEGER - 16-bit signed value
    POINTER - 8086 style segment:offset machine address


    So how about...

    BYTE for 8 bits
    DBYTE for 16 bits
    WORD for 32 bits
    DWORD for 64 bits
  • cgraceycgracey Posts: 14,206
    edited 2017-02-28 09:23
    A few words that have been used to describe variables in other languages.

    From PL/M80, which ran on 8-bit CPUs...
    BYTE - 8-bit unsigned value
    ADDRESS - 16-bit unsigned value

    From PL/M86, which ran on 16-bit CPUs...
    BYTE, as above
    WORD - 16-bit unsigned value
    DWORD - 32-bit unsigned value
    INTEGER - 16-bit signed value
    POINTER - 8086 style segment:offset machine address


    So how about...

    BYTE for 8 bits
    DBYTE for 16 bits
    WORD for 32 bits
    DWORD for 64 bits

    But we're in the future now and things turned out differently.

    How about:

    BIT = 1 bit
    TWIT = 2 bits (that's twin-bit)
    NIBBLE = 4 bits
    BYTE = 8 bits
    BYRD = 12 bits (that's between byte and word, PIC16C5x series had BYRD instructions)
    WORD = 16 bits
    WONG = 24 bits (that's between word and long)
    LONG = 32 bits
    DONG = 64 bits (that's double-long)
  • cgracey wrote: »
    BIT = 1 bit
    TWIT = 2 bits (that's twin-bit)
    NIBBLE = 4 bits
    BYTE = 8 bits
    BYRD = 12 bits (that's between byte and word, PIC16C5x series had BYRD instructions)
    WORD = 16 bits
    WONG = 24 bits (that's between word and long)
    LONG = 32 bits
    DONG = 64 bits (that's double-long)

    Unless there are underlying opcodes that directly support those data types then there's little point.
  • Chip,
    One good solution to your example problem is the interface model. It'll take forever to explain completely, but it's a rather simple thing in the end.

    You define an interface similarly to an object, just without any actual method bodies. Then you can "derive" your object from the interface. Then the interface is the only thing the "user" object needs to know about, and multiple different objects can "derive" from the interface (sometimes called implementing the interface), and be used interchangeably.

    So the interface for your example would define a method that receives characters, and you would have the function that sends the characters know about the interface (like an obj) and call the interfaces method to receive the chars. Then you could make any object derive from the interface and actually implement the method that receives the characters, and one of those objects is what you would pass in in place of the interface. Does this make sense?

    It's actually more flexible because you can define multiple functions in the interface instead of just one, but you can still make many objects that all implement/derive from the interface.

    I hope this makes sense and you like it because it's one of my favorite mechanisms to use for abstraction.
  • cgraceycgracey Posts: 14,206
    Roy Eltham wrote: »
    Chip,
    One good solution to your example problem is the interface model. It'll take forever to explain completely, but it's a rather simple thing in the end.

    You define an interface similarly to an object, just without any actual method bodies. Then you can "derive" your object from the interface. Then the interface is the only thing the "user" object needs to know about, and multiple different objects can "derive" from the interface (sometimes called implementing the interface), and be used interchangeably.

    So the interface for your example would define a method that receives characters, and you would have the function that sends the characters know about the interface (like an obj) and call the interfaces method to receive the chars. Then you could make any object derive from the interface and actually implement the method that receives the characters, and one of those objects is what you would pass in in place of the interface. Does this make sense?

    It's actually more flexible because you can define multiple functions in the interface instead of just one, but you can still make many objects that all implement/derive from the interface.

    I hope this makes sense and you like it because it's one of my favorite mechanisms to use for abstraction.

    I think I get it. I was actually supposing something like that could be done, but not sure how. Still not sure, but it feels doable. Thanks for explaining that.
  • Chip,
    An interface would be an object that just has PUB declaration lines, no actual code. Then in another object you need a way include the interface object as an interface that it implements.

    Perhaps this:

    myInterfaceObject.spin
    PUB InterfaceFunc1(Param1, Param2)
    PUB InterfaceFunc2(Param1)
    

    ImplementingObject.spin
    OBJ
      interface <- "myInterfaceObject.spin"
    
    PUB InterfaceFunc1(Param1, Param2)
      'some code
    
    PUB InterfaceFunc2(Param1)
      'some code
    

    UserObject.spin
    OBJ
      myInterface -> "myInterfaceObject.spin"
    
    PUB main(anyObjectPtr)
      myInterface[anyObjPtr].InterfaceFunc1(1,2)
      myInterface[anyObjPtr].InterfaceFunc2(100)
    

    main.spin
    OBJ
      test : "ImplementingObject.spin"
      user : "UserObject.spin"
    
    PUB main
      user.main(test)
    
  • cgraceycgracey Posts: 14,206
    Thanks, Roy.
  • cgraceycgracey Posts: 14,206
    I'm working out how object instantiation vs. type declaration works and I realized that in either case, you include the object (the distiller removes any replicas later), but in the case of instantiation, you don't allocate its VAR space. That connection gets made at run time.
  • cgracey wrote: »
    The lingering issue is that we need a double-long variable to hold a full object pointer. An object pointer could just be declared via 'VAR long Ptr[2]' and passed around as '@Ptr'.

    If I understand correctly, the two pointers are vbase and pbase.

    It only makes sense to use an object instance's VAR block with that objects' own methods.

    Therefore, if you make the first long of the vbase of each object a pointer to that object's pbase, object pointers can be made to fit in a single long containing a pointer to the object's vbase.

    pbase := long[vbase][0]

    This is the solution that most object-oriented programming languages use to fit object references in a single pointer. It's generally called a vtable, since it's used to store virtual methods. In Spin, all methods are virtual, i.e. they get looked up in the method table on every invocation.
    Very strange. I was in Rocklin talking with Chip and Steve Denson (jazzed) a number of years ago where we had this exact same discussion including the same syntax for object pointer access and the idea of using the vtable approach. I guess it was premature at that point. :-)
  • Roy Eltham wrote: »
    Chip,
    One good solution to your example problem is the interface model. It'll take forever to explain completely, but it's a rather simple thing in the end.

    You define an interface similarly to an object, just without any actual method bodies. Then you can "derive" your object from the interface. Then the interface is the only thing the "user" object needs to know about, and multiple different objects can "derive" from the interface (sometimes called implementing the interface), and be used interchangeably.

    So the interface for your example would define a method that receives characters, and you would have the function that sends the characters know about the interface (like an obj) and call the interfaces method to receive the chars. Then you could make any object derive from the interface and actually implement the method that receives the characters, and one of those objects is what you would pass in in place of the interface. Does this make sense?

    It's actually more flexible because you can define multiple functions in the interface instead of just one, but you can still make many objects that all implement/derive from the interface.

    I hope this makes sense and you like it because it's one of my favorite mechanisms to use for abstraction.
    Yup! I was just thinking about this last night but didn't have time to put together a post. I think Roy probably explained it better than I could anyway. Good idea! If you only allow an OBJ to implement a single interface this is pretty easy. It becomes hard if a single OBJ can implement multiple interfaces.
  • cgraceycgracey Posts: 14,206
    edited 2017-02-28 11:53
    I just figured out how to get this working. It's DIRT simple.


    Note: ObjAddr and HookObj are built-in Spin methods that facilitate object sharing.
    ' Top-Level Object
    
    OBJ
    
      w  : "Whizbang"
      b  : "Boomerang"
    
    PUB go
    
      b.go(ObjAddr(w))	'pass VAR instance address of Whizbang
    
    ' Boomerang Object
    
    OBJ
    
      w  = "Whizbang"	'no VARs reserved for this Whizbang
    
    PUB go(varptr)
    
      HookObj(w, varptr)	'child's Whizbang now same as parent's Whizbang
    

    All that is needed is a means to tweak VAR pointers within objects. And a way to extract VAR pointers. You could even repoint existing objects, whether or not they actually had their VARs allocated.
  • cgracey wrote: »
    Roy Eltham wrote: »
    Chip,
    One good solution to your example problem is the interface model. It'll take forever to explain completely, but it's a rather simple thing in the end.

    You define an interface similarly to an object, just without any actual method bodies. Then you can "derive" your object from the interface. Then the interface is the only thing the "user" object needs to know about, and multiple different objects can "derive" from the interface (sometimes called implementing the interface), and be used interchangeably.

    So the interface for your example would define a method that receives characters, and you would have the function that sends the characters know about the interface (like an obj) and call the interfaces method to receive the chars. Then you could make any object derive from the interface and actually implement the method that receives the characters, and one of those objects is what you would pass in in place of the interface. Does this make sense?

    It's actually more flexible because you can define multiple functions in the interface instead of just one, but you can still make many objects that all implement/derive from the interface.

    I hope this makes sense and you like it because it's one of my favorite mechanisms to use for abstraction.

    I think I get it. I was actually supposing something like that could be done, but not sure how. Still not sure, but it feels doable. Thanks for explaining that.
    I think the key is that the interface describes how the methods should be ordered in the vtable so that someone calling one of the interface methods always uses the same offset into the vtable to get the method pointer regardless of what type of object is passed.
  • cgraceycgracey Posts: 14,206
    David Betz wrote: »
    cgracey wrote: »
    Roy Eltham wrote: »
    Chip,
    One good solution to your example problem is the interface model. It'll take forever to explain completely, but it's a rather simple thing in the end.

    You define an interface similarly to an object, just without any actual method bodies. Then you can "derive" your object from the interface. Then the interface is the only thing the "user" object needs to know about, and multiple different objects can "derive" from the interface (sometimes called implementing the interface), and be used interchangeably.

    So the interface for your example would define a method that receives characters, and you would have the function that sends the characters know about the interface (like an obj) and call the interfaces method to receive the chars. Then you could make any object derive from the interface and actually implement the method that receives the characters, and one of those objects is what you would pass in in place of the interface. Does this make sense?

    It's actually more flexible because you can define multiple functions in the interface instead of just one, but you can still make many objects that all implement/derive from the interface.

    I hope this makes sense and you like it because it's one of my favorite mechanisms to use for abstraction.

    I think I get it. I was actually supposing something like that could be done, but not sure how. Still not sure, but it feels doable. Thanks for explaining that.
    I think the key is that the interface describes how the methods should be ordered in the vtable so that someone calling one of the interface methods always uses the same offset into the vtable to get the method pointer regardless of what type of object is passed.

    I'll think about the interface thing after I sleep. It's almost 4am here.

    I do remember that conversation, David. It turns out we don't even need to worry about compound pointers in any form, because we can just redirect vbase pointers within the object table. We could even redirect pbase pointers to get interfacing.
  • cgraceycgracey Posts: 14,206
    edited 2017-02-28 12:03
    Wait! I just realized something. We'd need to get our object table into VAR space, in order not to clobber other instances of the object code. This takes more thinking.

    Back to the vtable concept.
  • cgracey wrote: »
    David Betz wrote: »
    cgracey wrote: »
    Roy Eltham wrote: »
    Chip,
    One good solution to your example problem is the interface model. It'll take forever to explain completely, but it's a rather simple thing in the end.

    You define an interface similarly to an object, just without any actual method bodies. Then you can "derive" your object from the interface. Then the interface is the only thing the "user" object needs to know about, and multiple different objects can "derive" from the interface (sometimes called implementing the interface), and be used interchangeably.

    So the interface for your example would define a method that receives characters, and you would have the function that sends the characters know about the interface (like an obj) and call the interfaces method to receive the chars. Then you could make any object derive from the interface and actually implement the method that receives the characters, and one of those objects is what you would pass in in place of the interface. Does this make sense?

    It's actually more flexible because you can define multiple functions in the interface instead of just one, but you can still make many objects that all implement/derive from the interface.

    I hope this makes sense and you like it because it's one of my favorite mechanisms to use for abstraction.

    I think I get it. I was actually supposing something like that could be done, but not sure how. Still not sure, but it feels doable. Thanks for explaining that.
    I think the key is that the interface describes how the methods should be ordered in the vtable so that someone calling one of the interface methods always uses the same offset into the vtable to get the method pointer regardless of what type of object is passed.

    I'll think about the interface thing after I sleep. It's almost 4am here.

    I do remember that conversation, David. It turns out we don't even need to worry about compound pointers in any form, because we can just redirect vbase pointers within the object table. We could even redirect pbase pointers to get interfacing.
    Yup! We talked about that as well. I seem to recall that you didn't want to do that because not all objects would be used through references or pointers and the vtable pointer would waste space in those cases. It sounds like Roy's idea that that pointer only be present for objects declared with "type" might solve that although it might make the compiler more complicated having to deal with both object storage layouts.
  • I think I saw an earlier version of one of your messages, Chip, where you said in that:
    OBJ
       SerType = "Parallax Serial Terminal"  ' abstract object type
       SerObj  : "Parallax Serial Terminal"  ' concreate object
    
    The difference is that for "SerObj" we instantiate one object, whereas for "SerType" we instantiate none. Otherwise everything is the same -- you include the method pointers in both cases. So in code like:
      PUB sendchar(t, c)
         SerType[t].tx(c)
    
    The method lookup for the "tx" method happens in the SerType table, which is already there.

    That lets us pass concrete objects around and call methods on them. What's still missing is an interface or a way to pass arbitrary object pointers. More on that in my next message.

    Eric
  • cgraceycgracey Posts: 14,206
    edited 2017-02-28 12:51
    Here is some really simple syntax we can use to redirect the VAR instance space:

    ObjName.MethodName(params) 'normal object usage
    ObjName(VarPtr).MethodName(params) 'redirected-VAR object usage
  • cgraceycgracey Posts: 14,206
    Eric, we were thinking the same thing. I chose parentheses instead of brackets, because brackets may already be present in selecting a member of an object array.
  • cgracey wrote: »
    Eric, we were thinking the same thing. I chose parentheses instead of brackets, because brackets may already be present in selecting a member of an object array.
    I liked brackets because they mirror the LONG[p] usage that is already part of Spin. That syntax already indicates pointer dereference and can just be extended by adding these new objects types to the built-in types of LONG, WORD, and BYTE that are already supported in Spin1.
  • cgraceycgracey Posts: 14,206
    David Betz wrote: »
    cgracey wrote: »
    Eric, we were thinking the same thing. I chose parentheses instead of brackets, because brackets may already be present in selecting a member of an object array.
    I liked brackets because they mirror the LONG[p] usage that is already part of Spin. That syntax already indicates pointer dereference and can just be extended by adding these new objects types to the built-in types of LONG, WORD, and BYTE that are already supported in Spin1.

    I like brackets better, too, but how to handle the idea that objects can be in arrays?
  • ersmithersmith Posts: 6,068
    edited 2017-02-28 13:26
    Let's look at a concrete example: a hex object that can print a number as hex to any output device. There are two ways to approach this: method pointers, or inheritence. Let's look at method pointers first.

    For a method pointer we need 3 things: (1) a pointer to the table of functions for the object (vtable), (2) an offset into that table for the particular method we will call, and (3) a pointer to the VAR data of the particular object that will be handling the call. (1) and (2) can actually be combined to give just a single pointer. That is, given a table of functions FUNCTAB and an offset into the table i we can calculate @FUNCTAB, or even do the lookup and just get FUNCTAB to get a pointer to the final code to call. So we're down to having to pass two pointers for a method: a pointer to the method's code, and a pointer to the object data. These could be passed as two variables. The syntax could look like:

    ' send a char using the func method of object ser
    PUB sendchar( ser.func, val )
       ser.func( val )
    

    The sendchar function really takes three parameters; the "ser.func" notation in the parameter list is just syntactic sugar (although we could also use it for type checking in the compiler if we wanted to). In the body of the PUB the "ser.func" notation would currently trigger an error, since "ser" and "func" are just regular variables. In the new compilier instead of triggering an error this would construct a call using "ser" as the object var pointer and "func" as a pointer to the method. If we were compiling to PASM this would look something like:
       push objptr        ' save current object pointer
       mov  objptr, ser   ' set new current object
       push val           ' set parameter
       call *func         ' indirect call through register func
       pop  objptr        ' restore object pointer
    

    Here's how a generic "hex" object would be created with this scheme:
    VAR
      long baseObj  ' data of object used for printing chars
      long txFunc   ' method used to print one char
      
    '' Print a hexadecimal number
    PUB hex(value, digits)
      value <<= (8 - digits) << 2
      repeat digits
        baseObj.txFunc(lookupz((value <-= 4) & $F : "0".."9", "A".."F"))
    
    PUB init(theobj.method)
      baseObj := theobj
      txFunc := method
    
    and to use we'd do something like:
    OBJ
       fds : "FullDuplexSerial"
       t:     "hex.spin"
    ...
       t.init(@fds.tx)  ' pushes pointers to fds data and tx method code
       t.hex($12345, 8)
    
Sign In or Register to comment.