Procedure variables or callbacks in spin?
ManAtWork
Posts: 2,176
Hello,
is it possible to write a "generic" method in spin that takes another method as an argument? For example I'd like to write a universal measurement function that calculates·an average of the value to measure. It·takes the address of the input variable and the number of values to take for the average as arguments.
Now, because of different update rates I'd like to make the "wait for next value" method variable. This way I could pass another method as argument that waits for a pin to toggle, for a varible to change or for a lock to be set. If this is not possible I'd have to rewrite several versions of the method or use lots of if or case statements. This would also be possible but is not nearly as elegant and prevents the creation of independent and reusable library objects.
I know I can jump to arbitrary destination adresses in assembler but this can't be mixed with spin. Is there some hack that allows patching of the method call vector in spin? Since all spin code resides in RAM it should be possible·somehow, like self modifying code in assembler. Of course, we have to assure that stack usage (number of arguments passed) is compatible.
·
is it possible to write a "generic" method in spin that takes another method as an argument? For example I'd like to write a universal measurement function that calculates·an average of the value to measure. It·takes the address of the input variable and the number of values to take for the average as arguments.
PUB average (inPtr, num): avg | sum repeat num waitNextVal sum+=long[noparse][[/noparse]inPtr] avg:= sum/num
Now, because of different update rates I'd like to make the "wait for next value" method variable. This way I could pass another method as argument that waits for a pin to toggle, for a varible to change or for a lock to be set. If this is not possible I'd have to rewrite several versions of the method or use lots of if or case statements. This would also be possible but is not nearly as elegant and prevents the creation of independent and reusable library objects.
I know I can jump to arbitrary destination adresses in assembler but this can't be mixed with spin. Is there some hack that allows patching of the method call vector in spin? Since all spin code resides in RAM it should be possible·somehow, like self modifying code in assembler. Of course, we have to assure that stack usage (number of arguments passed) is compatible.
·
Comments
Edit:· I thought about this some more, and realized that a dummy CallFunction would be needed for each cog so there wouldn't be any collisions.· Multiple CallFunction's would also be needed to handle each variation of argument count that is used.· A pre-processor could hide all these nasty details from the programmer.
con
· main_index 0
· sub1_index 1
· CallFunction_index 2
· SetFunctionPointer_index 3
pub main | FunctionIndex
· FunctionIndex := sub1_index
· SetFunctionPointer(FunctionIndex)
· CallFunction(arg1, arg2)
pub sub1(x, y)
· ...
pub CallFunction(x, y)
pub SetFunctionPointer(func_index)
· ' Copy function info for func_index to CallFunction table entry
Post Edited (Dave Hein) : 3/2/2010 3:05:13 PM GMT
ok, let's just ignore the problems of multitasking and variable number of arguments at the moment. I think there must be a simpler solution.·Something like...
Assuming that CallDummy is the first (and only) instruction of CallStub, there should be a·fixed offset to add where the adress of the CallDummy destination is located in memory. So the CallDummy would be overwritten with the desired destination. I know very little about the internals of the spin interpreter, but it's in ROM and therefore, at least, it shouldn't change.
Cheers
http://forums.parallax.com/showthread.php?p=593662
It's dated and crude, but may be useful nonetheless.
-Phil
2. Function calls are translated to an index inside of a function address table by the SPIN compiler. Such an function address table exists for each object.
So, in the end you need a lot of hardcore memory access if you want to implement something like that. So, it's much easier to use a case statement instead.
The only caveats are:
Variable args can be implemented using an object with a queue datastructure.
Syntax would be similar to Java's System.out.print(...). For example:
Post Edited (jazzed) : 3/2/2010 9:24:15 PM GMT
Spin does not allow for directly accessing a method's address.· However, you can get it from the call table, and modify it to call another method.
Method calls are described in the wiki at http://propeller.wikispaces.com/Method+Calls .· Method calls use a table that contains the address and local variable size for each PUB method followed by each PRI method.· I believe the program shown below would call sub5 using sub2's table entry.· I haven't tried it myself.
CON
· ' Define an index for each PUB method
· sub1_index = 1
· sub2_index = 2
· sub5_index = 3
· ' Define an index for each PRI method
· sub3_index = 4
· sub4_index = 5
PUB sub1 | MethodTablePtr, MethodAddr, VariableSpace
· ' Initialize to the address of the Method Table
· MethodTablePtr := $10
· ' Get the address and local variable size for sub2
· MethodAddr := word[noparse][[/noparse]MethodTablePtr][noparse][[/noparse]sub2_index*2]
· VariableSpace := word[noparse][[/noparse]MethodTablePtr][noparse][[/noparse]sub2_index*2 + 1]
· ' Copy sub2's info to the entry for sub5
· word[noparse][[/noparse]MethodTablePtr][noparse][[/noparse]sub5_index*2] := MethodAddr
· word[noparse][[/noparse]MethodTablePtr][noparse][[/noparse]sub5_index*2 + 1] := VariableSpace
· ' Call sub2 using sub5's table entry
· sub5(1, 2)
PUB sub2(x, y) | z
PRI sub3(x, y, z)
PRI sub4 | x
PUB sub5(x, y)
thanks a lot. At least it seams to be possible somehow. But I think here applies the proverb "Most time is wasted by trying to save time". The "unelegant" way with lots of case statements which is disapproved in true object oriented languages·seems to be more clearly and easier, here.
Cheers
Since Spin does not provide direct access to the method address or its index number in the table I had to create a set of constants for the method indexes.· The test program sets the function pointer for sub1, sub2 and sub3, and calls "CallFunction" to execute the function.
I determined the location of the call table by using a value in the DAT section that stores its own address.· The stored value does not contain the relocation offset.· The address obtained at run time does contain the offset.· The call table is stored at the first location after the offset, so I can compute the function table address by computing the difference @memaddr - memaddr.· I believe this technique can be used for additional objects, but I did not test it.
Thanks for the tip.· So the call to SetFunctionPtr can be replaced by the following line:
long[noparse][[/noparse]@@0][noparse][[/noparse]CallFunction_index] := long[noparse][[/noparse]@@0][noparse][[/noparse]function_index]
Dave
Just one concern: I don't think the method address is a long.
Added: A method pointer with no parameters and no local variables will look like a long since the
upper word would be 0 or $0000_xxxx. It seems method with 1 local variable would be $0004_xxxx.
There are various references to Spin object structure. Here is a thread that may help:
http://forums.parallax.com/showthread.php?p=736449
Post Edited (jazzed) : 3/3/2010 4:55:54 PM GMT
I looked at your code, and I think I understand the basic idea.· However, there are many details in your code that I still haven't quite figured out.· I know that having multiple objects complicates things quite a bit.· Your dummy routines are in a different object than the routines that actually get executed.· This requires adjusting the object offset and the VAR offset for the dummy routine.· You are also modifying the index within the spin bytes instead of in the call table.· It seems like things would simplify if the dummy routines are in the same object as the routines that are called.· It is also easier to figure out where the call table entry is, and modify that rather than modifying the index number·within the spin bytes.
I haven't figured out how the call table works for multiple objects, but it seems like the technique I'm using should be extendable to multiple objects.· I think it can also be extended to multiple cogs by using multiple dummy routines, with one dedicated to each cog.
Dave
Thanks for the link to the thread.· I copy the local variable count because I assume the spn code will add this to the SP when it calls a method.· I'll have to add some prints to verify that it works correctly.· I am assuming the 2 words used for a method table entry are long aligned, so I copy them together as a single long.· If this is not always the case then they need to be copied seperately as 2 words.
Dave
What you're seeing makes sense. There may be some combination of -O options that would work though.
I'm not sure if BST has controls for all of the options or not; I always used BSTC from the command line.
bstc gives this if you haven't seen it ...