Call spin routine by pointer?
Dennis Ferron
Posts: 480
Is it possible to call a spin routine via a pointer? Like how you pass the name of a routine to cognew.
I was thinking it would be useful to have an object that executes serial commands, and to make it generic, it would take a command number and a pointer to a spin routine to execute when that command number comes in. Something like:
I was thinking it would be useful to have an object that executes serial commands, and to make it generic, it would take a command number and a pointer to a spin routine to execute when that command number comes in. Something like:
OBJ ser_cmd : "Serial_Command" PUB Demo ser_cmd.start(31, 30, 19200) ser_cmd.add(1, @ActionOne) ser_cmd.add(2, @ActionTwo) ' Wait for commands to come in repeat ser_cmd.DoCmd PRI ActionOne ' Do stuff when a "1" comes in PRI ActionTwo ' Do other stuff when a "2" comes in
Comments
Cheers
▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
--Steve
Propalyzer: Propeller PC Logic Analyzer
http://forums.parallax.com/showthread.php?p=788230
It should be noted that Steve's solution is not a supported feature of the language, despite its irresistable cleverness. Just make sure that your argument count among the various object methods is the same, as must be their relative position (definition order) within each object.
-Phil
Steve's solution is remarkable (I didn't realize you could have arrays of objects in Spin!) but has limitations as Phil points out. You could make it work but not for multiple subroutines in the same object.
An old x86 trick involves pushing a desired address onto the stack, and then executing a ret instruction without a corresponding call first. A ret is a just a pop and a jump, so you can trick the CPU into jumping to an arbitrary address when it executes the return. Maybe this could be accomplished with a similar stack hack? Like this:
In principle this could work if GetReturnAddr works. It's similar to the kind of thing malicious code has to do to actually exploit a buffer overrun vulnerability in an app. Of course there's a lot of caveats to this too - the presence and number of function arguments will affect things, and you have to clean up the stack when you're done.
Unfortunately that's all we got for now. I figured from your first post you knew the dangers of having such a "rope" [noparse]:)[/noparse]
I believe the address of "result" is the current stack pointer ....
The current function address can be obtained with a statement like: addr := word[noparse][[/noparse]@result-2].
The return address would be: retaddr := word[noparse][[/noparse]@result-4] .
You may find it useful to look at the spin interpreter for digging in ... it is posted on the forums:
http://forums.parallax.com/forums/default.aspx?f=25&m=252691
▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
--Steve
Propalyzer: Propeller PC Logic Analyzer
http://forums.parallax.com/showthread.php?p=788230
Unfortunately that simple hack will not work in Spin as calls are not performed to the address of the target method but are numeric indexes into a jump table, and the jump table also contains the equivalent of 'stack frame' adjustments which need to be executed before the call.
There are two alternative hacks; patching the executable bytecode at run-time ( not good for code simultaneously executed by two cogs ) or patching the jump table ( copying required data into a dummy entry ). Neither are simple and require an understanding of the object code layout. There should be additional information in previous forum posts.
I'm not sure if this is along the lines of what you are looking for, but this example allows an Assembly program to run Spin Code by using a Spin reference pointer and either Exit the Assembly code and return back to Spin, or return back to the Assembly code after the Spin code has completed. By returning back to the Assembly code you could check for other command values, and set it up to function like your original example, but I guess I don't see how that's entirely different than using the Spin CASE command.
▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
Beau Schwabe
IC Layout Engineer
Parallax, Inc.
Post Edited (Beau Schwabe (Parallax)) : 4/13/2009 5:22:40 AM GMT
The difficulty is that we're not just talking about making a cog do something, where assembly lets us do whatever we want; we have to make the Spin interpreter *program* running in its own cog do something it wasn't designed to do.
However, the *intent* of your example is probably right in how it would have to work. Of all the caveats that the other approaches have, the caveat that the executed command runs in a separate cog is probably the least onerous. When a command comes in you launch an assembly cog that in turn loads a coginit/cognew/cogstart whatever, and wait for the command to finish. Does the cog automatically shut down when it comes to the end of a Spin program? Because then you might be able to monitor the launched cog and know the command is done when the cog halts.
In regards to how it's different than the Spin CASE command, let me make an analogy. How is:
Different from:
Both "do" the same thing, but in the first example SetValue could be in one object and used by a completely different object, whereas the second example has to "know" about the variable x and can't be used to say, set the value of Y.
This is the same way: in a CASE statement you put the code you want executed in their literally like in the second example above; I want the user to be able to change things at runtime like the first example above. I can't stress enough that we don't even know ahead of time what that code will be - that's to be set by the user of the command processor. You can't use a CASE statement if the code to execute in each case doesn't even exist yet when the object containing the CASE command is written.
I'm very confused by your example. The jmpret t1, #WeAreBackFromSpin simply jumps to #WeAreBackFromSpin and deposits the return address in t1, overwriting the source field. I can't see any mechanism there for executing a Spin routine. I believe that DEMO_Test is getting executed before the cognew is executed (i.e. the value it returns get put into PAR).
-Phil
this is magic. I'm as confused as you, but he seems to tell us the truth. I added some lines of code:
First of all the program waits until I hit any key in the terminal.
The original SPIN COG then sets output pin 3 (which is a LED output on my board). And yes, the LED is on. After the cognew I added some lines which make the LED blink. This should normally happen very fast. But here it waits until the DEMO_Test has been executed. Then the LEDs set by the PASM blink one time and the LED driven by SPIN starts blinking.
I did some more changes and let the PASM code blink the LEDs endlessly instead of doing COGSTOP. And the blink.
Conclusion:
By magic the ...... got it!
Last test showed it. I made a comment out of the jmpret. If this would be the code that starts the SPIN, then now the test-strings should not be send. But they are.
What happens is that SPIN itself first tries to solve all parameters. You can have numerical expressions in each of the cognew parameters. These will be solved during runtime. The second parameter is a function. Solving a function means to call it and pass the return-value of the function as parameter. (Phil, you are right)
Beau, I really was willing to believe you, but in the end the program only gave you the look and feel of what you want, but it works differently.
It's really amazing how tricky some bugs are. It really looks like working as desired after several changes of the code.
Post Edited (MagIO2) : 4/12/2009 9:22:47 PM GMT
SPIN already has a table with the jump-adresses of all PUB and PRI functions. A call has a bytecode opcode and as parameter an index from that table. So you have to identify the call and place another index as parameter.
rough scetch of the idea:
This code has not been tested.
When calling·'call' first time, it searches for a pattern in memory. As the values of an assignment are really stored within the SPIN code, such an assignment can be used to·locate·code adress in memory. Of course this could be put in a different function.
Once the adress has been found, the parameter of the call (opcode $05) only has to be replaced.
The index simply has the same order than the functions have in the SPIN-file.
Post Edited (MagIO2) : 4/12/2009 10:24:42 PM GMT
▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
Beau Schwabe
IC Layout Engineer
Parallax, Inc.
I've never really looked at the stack framing but doesn't it basically store pcurr, dcurr and vcurr (plus something that I'm forgetting, stack pointer maybe?)? If it does than shouldn't you be able to change these three or four values and then when the method returns it will run some arbitrary code? It wouldn't really be a function call because you may not be able to guarantee that any variables on the stack kept their proper values. Getting back out of the arbitrary code could also be a problem...
Something along these lines (not tested and won't work, just to give the general idea)
is the same as for any other language, push 'state information', call method, restore 'state' and return
with result on stack if there is any result. Albeit that getting to the method involves a call via the jump
table.
This is a single 'via jump table' for a call into a method within the same object, a double-via for a call
to a method in a different object. I cannot recall exactly what adjustments are made in on the way in
to a method using the data in the jump table but recall it adjusts the base pointer for the start of an
object's global variables. Make a call without adjusting things correctly and subsequent access to
global vars is to memory in the wrong places.
There's also another way to alter the PC by hacking bytecode to access the PC ( or SP ) within the
interpreter Cog by use of "SPR" functions ...
http://forums.parallax.com/showthread.php?p=739430
Of course, there's no reason not to run a clone of the Spin Interpreter rather than the ROM version,
intercept calls and handle them through some user supplied mechanism.
Bottom line is; function pointers can be achieved, there are a number of mechanisms, but none of them
are simple to implement, there's no definitive implementation, and the PropTool doesn't emit any
information which makes the task easy or generic. Some work with re-entrant code, others do not.
I've looked a fair bit at dynamically loading spin objects from an sd card and have had it working reasonably well and also have ideas for how inheritance could be supported. However, since the compiler doesn't support a couple of things that need doing it's either a case of write a pre-processor or compiler to make it useful because there ends up being too many numbers that have to be put in by hand.
Like hippy said, it's possible and actually not that hard to demonstrate the necessary techniques but it is a lot harder to make something which is 'coder friendly' and doesn't have unintended side affects.