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.
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.
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?
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.
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?
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.
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
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)
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.
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.
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.
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. :-)
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.
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.
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.
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.
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.
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, 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.
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.
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?
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)
Comments
They could plan for this and reuse memory.
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.
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?
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
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.
But that breaks decades of convention where WORD is the native data size of the CPU.
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
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)
Unless there are underlying opcodes that directly support those data types then there's little point.
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.
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
ImplementingObject.spin
UserObject.spin
main.spin
Note: ObjAddr and HookObj are built-in Spin methods that facilitate object sharing.
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.
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.
Back to the vtable concept.
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
ObjName.MethodName(params) 'normal object usage
ObjName(VarPtr).MethodName(params) 'redirected-VAR object usage
I like brackets better, too, but how to handle the idea that objects can be in arrays?
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:
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:
Here's how a generic "hex" object would be created with this scheme: and to use we'd do something like: