Call spin routine by pointer?
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
CON ' Object Overload function pointer call example. ' Takes advantage of Spin Compiler's lack of object array boundary checking. _clkmode = xtal1 + pll16x _xinfreq = 5_000_000 OBJ ob : "object" ' ob[noparse][[/noparse]0] ob1 : "object1" ' ob ob2 : "object2" ' ob ob3 : "object3" ' ob OBJ fd : "FullDuplexSerial" PUB main | n fd.start(31,30,0,115200) fd.rx ' wait for user input fd.str(string($d,"Object Overload Function Pointer Demo",$d)) repeat n from 0 to 3 fd.dec(action(n)) ' print numbers returned by object function fd.tx(" ") repeat PUB action(index) return ob[noparse][[/noparse]index].functionCheers
▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
--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.
[s][b]CON[/b] ''General Constants for Propeller Setup [b]_CLKMODE[/b] = [b]XTAL1[/b] + [b]PLL16X[/b] [b]_XINFREQ[/b] = 5_000_000 [b]OBJ[/b] ''Setup Object references that make this demo work Ser : "FullDuplexSerial" [b]PUB[/b] Main_Program Ser.start(31, 30, 0, 19200) '' Initialize serial communication to the PC [b]cognew[/b](@ASM_SpinRun,DEMO_Test) [b]PUB[/b] DEMO_Test ser.tx(0) ''Clear Display [b]repeat[/b] 500 ser.[b]str[/b]([b]string[/b]("[b]Test[/b] ")) ''Print "Test" 500 times [b]DAT[/b] ASM_SpinRun [b]mov[/b] t1, [b]par[/b] [b]rdlong[/b] t1, t1 [b]jmpret[/b] t1, #WeAreBackFromSpin ''Call SpinCode from Assembly and return ' jmp t1 ''Call SpinCode from Assembly and don't come back ; Stops Assembly COG WeAreBackFromSpin [b]mov[/b] [b]outa[/b], mask ''Make ALL the LEDs HIGH [b]mov[/b] [b]dira[/b], mask ''Set ALL the LEDs on the demo board as output [b]add[/b] OneSecond, [b]cnt[/b] ''Wait for 1 second so we can see the LED's [b]waitcnt[/b] OneSecond, #0 [b]cogid[/b] t1 ''Stop COG [b]cogstop[/b] t1 t1 [b]long[/b] 0 mask [b]long[/b] %00000000_11111111_00000000_00000000 OneSecond [b]long[/b] 80_000_000 [/s]▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
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:
VAR long i PUB Main_Program Ser.start( 31, 30, 0, 57600 ) Ser.rx dira[noparse][[/noparse]3]:=1 outa[noparse][[/noparse]3]:=1 cognew(@ASM_SpinRun,DEMO_Test) repeat !outa[noparse][[/noparse]3] waitcnt( clkfreq/4 +cnt) PUB doinnothin dira[noparse][[/noparse]4]:=1 outa[noparse][[/noparse]4]:=1 PUB DEMO_Test ser.tx(0) ''Clear Display repeat i from 1 to 1000 ser.str(string("Test ")) ''Print "Test" 500 times ser.dec( i ) ser.str(string(" "))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:
PUB call( index ) | i if call_adr==0 ' search the pattern $AABBAABB in memory ' you have to do that bytewise, because longs don't need to be long aligned inside the bytecode ' end_of_dat and begin_of_var should be defined - gess where repeat whateveradress from @end_of_dat to @begin_of_var ' ** missing: do the pattern search and stop if found call_adr := whateveradress + offset to next SPIN instructions parameter (propably 5) else byte[noparse][[/noparse] call_adr+1 ] := index i:=$AABBAABB ' this is needed to find the call inside the bytecode OtherMethod ' this originally calls one of the messages in the indexThis 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.