Case statement problem
hippy
Posts: 1,981
I have the need to execute one of a number of methods ( subroutines ) depending upon a variable's value, and want to do this quickly and with minimum of code space. ideally I'm looking for a Spin equivalent of 'ON var GOSUB sub0, sub1, sub2,sub3".
It's easy enough to use a Case statement, but that comes with quite an overhead of execution time as it steps through each CASE bytecode ...
The best way I can see of improving execution speed is to have a single CALLSUB opcode and poke the required method's ID number ( 3rd byte ) at run time. That requires a certain amount of trickery to determine the ID numbers to start with.
All PUB and all PRI methods have consecutive ID numbers sequentially from one as they appear in source code, but would still need to find the first numbered if I did not want to put them at the top of the source file. By making all methods PRI and only the first and those called in this jump code PUB would be a simple 'idNumber := jumpIndex+2'.
Finding the address to poke to for the CALLSUB is another challenge, but might be achievable by peeking what's on the stack in a method called from within a Case statement ( the first PUSH $3A is the address of the RETURN less base address of the object, $10 because it's a top-level object ). Could get interesting when this isn't the top-level object, but let's start simple (sic) first.
It's all quite complex, and I expect reasonably pointless to ask, "has anyone solved this problem ?", but if anyone has any suggestions I'm open to them.
For the Propeller Tool, it would be a great boon if something like the following could be added, and it doesn't look to me that this would require anything more than appropriate code generation, so current Rom firmware should not be an issue ...
optionalVar := gosub( indexVar : Sub0, Sub1, Sub2, Sub3 )
It couldn't be implemented quite how I described because methods may be out of order, or even in sub-objects, but an indexed table jump to a method call would be much quicker than a Case or long sequence of If-Elseif.
Any chance of such a thing happening ?
It's easy enough to use a Case statement, but that comes with quite an overhead of execution time as it steps through each CASE bytecode ...
==== ; PUB CaseTest ==== ; Case result ==== ; 0 : Sub0 ==== ; 1 : Sub1 ==== ; 2 : Sub2 ==== ; 3 : Sub3 0028 38 3A S7 PUSH 58 002A 60 PUSH RESULT.LONG 002B 35 0D 0C CASE 0, J8 002E 36 0D 0D CASE 1, J9 0031 37 00 0D 0D CASE 2, J10 0035 37 21 0D 0D CASE 3, J11 0039 0C GOTO[noparse][[/noparse]] 003A 01 05 02 J8 CALLSUB S13 003D 0C GOTO[noparse][[/noparse]] 003E 01 05 03 J9 CALLSUB S14 0041 0C GOTO[noparse][[/noparse]] 0042 01 05 04 J10 CALLSUB S15 0045 0C GOTO[noparse][[/noparse]] 0046 01 05 05 J11 CALLSUB S16 0049 0C GOTO[noparse][[/noparse]] 004A 32 J12 RETURN
The best way I can see of improving execution speed is to have a single CALLSUB opcode and poke the required method's ID number ( 3rd byte ) at run time. That requires a certain amount of trickery to determine the ID numbers to start with.
All PUB and all PRI methods have consecutive ID numbers sequentially from one as they appear in source code, but would still need to find the first numbered if I did not want to put them at the top of the source file. By making all methods PRI and only the first and those called in this jump code PUB would be a simple 'idNumber := jumpIndex+2'.
Finding the address to poke to for the CALLSUB is another challenge, but might be achievable by peeking what's on the stack in a method called from within a Case statement ( the first PUSH $3A is the address of the RETURN less base address of the object, $10 because it's a top-level object ). Could get interesting when this isn't the top-level object, but let's start simple (sic) first.
It's all quite complex, and I expect reasonably pointless to ask, "has anyone solved this problem ?", but if anyone has any suggestions I'm open to them.
For the Propeller Tool, it would be a great boon if something like the following could be added, and it doesn't look to me that this would require anything more than appropriate code generation, so current Rom firmware should not be an issue ...
optionalVar := gosub( indexVar : Sub0, Sub1, Sub2, Sub3 )
It couldn't be implemented quite how I described because methods may be out of order, or even in sub-objects, but an indexed table jump to a method call would be much quicker than a Case or long sequence of If-Elseif.
Any chance of such a thing happening ?
Comments
Have got a self-modifying 'on-gosub' working but relies on knowing the address of the CALLSUB. Now to determine that address at run-time ...
The first 16 bytes of Ram ( where stack pointer initialisation pointers are for reboot / reset ) do not change no matter how deep in subroutine calls when checking them so they are read only and the stack pointer is within the Cog and thus inaccessible. Obvious with hindsight.
With a bit of twiddling and poking a POP bytecode into the executable code it is possible to extract the top of stack value and store in a variable. Unfortunately two problems; the address to put the POP bytecode needs to be found ( we're going round in circles now ), and it doesn't appear that the top of stack is the return address of the calling method. Presumably the return address is lower down the stack. So a simple 'pokeAddress := poppedReturnAddress-K' is out of the question.
There is a mechanism by which the image 'object tree' can be traversed so an address to poke the CALLSUB as originally envisioned can be found regardless of whether a top object or sub-object, but that's more work than I'm prepared to take on at present.
I've resorted to nested Case statements testing different bit fields of the index variable. It adds code bloat but increases average execution speed.
I have to admit some of that was quite beyond me and will take some reading, but I did find another way to 'skin the cat' courtesy of PhiPi and his stack usage monitoring concept ... Put a known word on the stack, then find it, and the return address is found below it. Only works for a top-level object and initialisation has to be called before the known word is accidentally put on the stack, but it works for me ...
Here's the thread:
http://forums.parallax.com/forums/default.aspx?f=25&m=165107