Spin feature request
stevenmess2004
Posts: 1,102
Is it possible to do the following in spin
Load an object from an SD card
run methods in that object
The first bit is relatively easy but the second will require a new function something like
runFunc(objectAddress,methodNumber,arg1,arg2....)
I realize that there would probably be no checking able to be done when compiled, but, imagine what could be done.
We could turn something like propdos into a proper OS instead of just a program that has to reboot the chip every time we want to change programs (not having a go at propdos, it's great, just making an observation).
Steven
Load an object from an SD card
run methods in that object
The first bit is relatively easy but the second will require a new function something like
runFunc(objectAddress,methodNumber,arg1,arg2....)
I realize that there would probably be no checking able to be done when compiled, but, imagine what could be done.
We could turn something like propdos into a proper OS instead of just a program that has to reboot the chip every time we want to change programs (not having a go at propdos, it's great, just making an observation).
Steven
Comments
For all that matter this would uncap memory limitations for all kinds of interesting uses..
OBC
▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
New to the Propeller?
Getting started with the Protoboard? - Propeller Cookbook
Got an SD card? - PropDOS
A Living Propeller FAQ - The Propeller Wiki
(Got the Knowledge? Got a Moment? Add something today!)
Although this could be done in some way or another. A simple design would load pASM code into the HUB-RAM and then lunch it. [noparse]:)[/noparse]
This PASM loader would work for simple things.
Spin would be a different story.
▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
Share the knowledge: propeller.wikispaces.com
Lets make some music: www.andrewarsenault.com/hss
Changes to the prop tool shouldn't be too much. Just another keyword and the ability to compile spin code without the 16 (byte?) header.
Do any of the games around do this kind of thing?
Might be accomplished quicker to create a working model of what you have in mind....
OBC
▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
New to the Propeller?
Getting started with the Protoboard? - Propeller Cookbook
Got an SD card? - PropDOS
A Living Propeller FAQ - The Propeller Wiki
(Got the Knowledge? Got a Moment? Add something today!)
I have been thinking about this; it seems a good idea but my first thoughts are, that could be hard. Two problems to overcome; where to load the object and how to link / call into it.
An executing image consists of Spin/PASM/DAT code from $0000 up, followed by VAR variable space, followed by stack up to $FFFF. Unless space were pre-allocated ( how big ? ) using a dummy object any loaded objects would have to be placed at end of RAM with perhaps some extra space added for object's stack. Some variable would need to exist to point to the first loaded object and each object would have to daisy chain to the next as a linked list. The pointer to the first could be any variable in the user program but a long at RAM top would perhaps be a better place.
Calling a loaded object could be done by specifying two numbers; the first the number of object as loaded and the second as an index into the list of methods to call. Both have difficulties because multi-cog programs could each be loading their own required objects so the order isn't known and a loaded object may load its own objects so the loaded order number cannot be a constant at compile time. For the method number indexing, PUB's are counted in the order they appear in source so it makes manually determining the index awkward and prone to error if anything changes ( 'DLL hell' ).
A good mechanism would be a call which allows the 'name' of the target called to be specified ( both object name and method name ). This could be a 32-bit signature ( four ascii chars, five 6-bit chars, six 5-bit chars ? ). Matching signatures would be held in each object and method. Calls 'by name' would be fixed-up on first execution self-modifying the Spin bytecode.
Obviously there's quite a lot of work there to be done and that is perhaps too much for the ROM Interpreter. An alternative interpreter could do it, but then so too could Spin or a Spin Object. I'd favour a Spin Object loading objects simply for the ease of debugging.
It requires some understanding of object image format but a Proof of Concept should be possible with what we have; include an object, copy it to top of RAM, use it from there. Named fix-ups can come later and requires some detailed knowledge of Spin bytecode and run-time stack use, and a Propeller Tool means of creating a 32-bit named signature in-source would help there.
If this is just a way to get a program into memory and executing then thinking in terms of dynamically loaded objects is perhaps wrong, although related. A main program is an object in its own right with an additional prefix. All objects are, unless I'm wrong, coded for position independence so why not just load them higher up in memory and execute them ?
It's a bit more complex in practice but probably not as complex as full dynamic object loading.
You can move spin objects around in memory and still run the methods in them although it is a bit of mucking around with pointers.
The attached program demonstrates moving an object, setting it's all the bits where it was originally stored to zero and then running some methods in the object.
Don't know what will happen if you try to access things in the VAR or DAT sections of the moved object yet so there is still plenty more to do but this is an encouraging start.
See attached
Accessing things in the HUB from asm routines might be a different matter though.
Post Edited (stevenmess2004) : 1/5/2008 8:02:17 AM GMT
I think that this is just about everything we need to dynamically load objects. Steps would be
1. Load object into some free space
2. Change pointer to next object in the just loaded object to point to the next object or start of VAR section
3. Change sub object pointer in main program from dummy object to object that has just been loaded
4. Change pointer in object before dummy object to point to the object that has just been loaded
5. Adjust VAR offset in new object to point to some empty space (needs more research)
These should probably all be done in a single method so that spin doesn't go into some weird place.
Depending on how spin handles sub-object calls, the new object could even be a group of objects. With a bit more playing around, it may even be possible to allow the new object access to already loaded objects (such as keyboard, display drivers).
There are four 'base pointers' used by the Spin Interpreter ...
Memory Base, always $0000
Object Base, $0010 for the main program
Variable Base, address of first VAR for the object
Local Variable Base, address of first local variable for current method ( subroutine, function )
All operations within the Spin Interpreter are relative to one of those bases. The 'result' variable is always the first local variable, parameters/arguments follow, then local variables.
Every object ( and main program ) starts with a 'method pointer table' which indicates where a PUB or PRI method is, and where other objects are located.
CALLOBJ has two operands, the first is an index into the current object's 'method pointer table', each entry is two words ( one long ) so CALLOBJ +5,+? means find the 5th entry after the first entry of the table ( +0 is the first entry ).
The first word there gives the object base of where the 'method pointer table' is in hub memory for the called object ( relative to the calling object's own 'object base' - $10 for the main program - so add $10 to it to find the actual hub address of the called object's 'method pointer table' ).
The second word is a byte count adjustment which needs to be added to the existing 'Variable Base'.
To enter an object's method, CALLOBJ locates the entry in the 'method pointer table', saves the current Object Base and Variable Base to stack, adjusts both of those according to the entry found.
It then uses the second operand to determine which method to execute for the new object. Again this is the same indexing into the ( sub-object's now ) 'method pointer table', the first word giving the address of the method ( offset by by the (sub-) Object Base ). The second word is a byte count of how much to increase the Stack Pointer, this reserves space for local variables.
Returning is a simple (!) matter of restoring Object, Variable and Local Bases to what they were prior to the call.
A look into AiChip_SpinVm_XXX.spin at FRAME/CALL/CALLOBJ should help clarify the exact workings. It was quite a slog getting that to work. What I push into a stack frame isn't exactly what the ROM Spin Interpreter does but does seem to work and is very close.
In theory it should be possible to include a dummy object in the main program ( eg, PropDos ) with just a PUB Main, then when an object needs to be loaded, the entry in the 'method pointer table' for the dummy object can be patched-up to point to the object being called. Thus a 'dummy.Main' call will become a call to 'loadedObject.Main'.
The only difficulty is that the adjustment to the Variable Base would probably ( I haven't investigated ) plonk the new object's variable's in the existing stack space used by PropDos. This shouldn't be a problem and can probably be overcome. One solution may be to Abort right back to PUB Main, trap the abort, call dummy.Main then restart SpinDos when it completes ...
As to the issue of the 'method pointer table' +0 entries forming a linked list of objects, I haven't determined why. If it wasn't needed it wouldn't likely be there, so there is probably some reason for it. It may explain the difference between SpinVm stack framing and the ROM Interpreter's.
@hippy Thanks for the info.
Just a couple other things
1. In the current implementation of spin, objects have a maximum of 32kB of program and 32kB of VAR. That means that the compiler, interpreter and object will have to change for the propII.
Here is a new copy of dynobj. This one demonstrates moving an objects VAR section around. Again, I haven't tried this on a prop yet, just in GEAR.
And now, I think I can make this work
Dynamic loading of objects from an SD card, with all dynamically loaded objects able to call other dynamically loaded objects. It is simply (!) a matter of a small amount of self modifying spin code. (That I think I can do)
Have a look at DOL.spin and testObj.spin and tell me what you think. They aren't finished yet but it would be helpful to get feedback because its easier to make changes now than later.
Any suggestions?
Almost certainly. I was thinking about that when writing SpinVM. At the moment word variables are often used to hold addresses and pointers ( 64KB addressing) and the Prop II will likely have 256KB.
That may not be such a problem though as objects, variable areas, stack bases are all aligned on long boundaries, so the bottom two lsb's are always zeroed, leaving only 14-bit data. 256KB requires 18-bit addressing but also long aligned so only 16-bits are actually needed. The 18-bit address can be stored right-shifted into 16-bit words and then left-shifted when needed for use.
However, if the pointer needs to point into ROM, above 256KB presumably, 18-bits isn't good enough, so words cannot be used unless alignments are adjusted to being double-long ( 64-bit ) aligned.
Words could still be used as pointers but with different usage. I think it will depend on how easy it is to alter the Spin Interpreter whether the same word-sized pointers are used or long-sized pointers.
The biggest impact is going to be on user code, and especially code in the Object Exchange, which may not work on the Prop II; no one can guarantee where an object/variable will be in memory, above or below the 64KB. Any code using word variables as pointers is going to run into potential problems. PASM coders aren't immune from problems either. I've started a separate thread on addressing that issue.
Next is to try combining objectStartPtr and objectVARBase into a long so that calling other objects will be quicker. After that will be working from an SD card.
I'm starting too get excited about this. We will be able to do fun things like event driven code with listeners and events and stuff.
Steven
Maybe someone with more knowledge can help me on one point though. In Peter's heap object on line 168 there is (was) a return. However, it never returned and just did the return at the end of the method. I changed it to an abort and it works fine. Does anyone know why?
largest available block and also > 0.
if (trytoallocate > heap.available)
· error(string("out of heap memory")
else
· if (trytoallocate =< 0)
··· error (string("illegal value"))
· else
··· addr := heap.allocate(trytoallocate)
where trytoallocate is the number of bytes that you would like to allocate.
regards peter
Just to let you know, I've been watching this thread - what you're doing looks really interesting; I'm sure others are watching too, in eager anticipation of the SD card integration. This appears to have HUGE possibilities for writing larger spin code, and could be something even _I_ could use (unlike the various LMMs which are PASM only).
Keep up the good work - I'll be lurking on this thread from time-to-time.
BTW: Can I make an early request for a step-by-step tutorial? I'd be happy to test-drive it / help write it; just let me know when you feel the time's right
▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
Cheers,
Simon
www.norfolkhelicopterclub.co.uk
You'll always have as many take-offs as landings, the trick is to be sure you can take-off again ;-)
BTW: I type as I'm thinking, so please don't take any offense at my writing style
Do you then get·a value other then 0? (you should)
regards peter
Steven
▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
Cheers,
Simon
www.norfolkhelicopterclub.co.uk
You'll always have as many take-offs as landings, the trick is to be sure you can take-off again ;-)
BTW: I type as I'm thinking, so please don't take any offense at my writing style
Steven