program calling with BS2
Archiver
Posts: 46,084
Hi,
Adding a simple stack mechanism allows for program calling instead of
jumping.
Both return points and function entries are handled in the same manner.
The setup described below allows for 256 return entries and 255 functions
per program.
By using a nibble for the stackpointer up to 4 nested program calls are
possible.
Utilizes 3 scratchpad bytes per call and just 4 variables.
Program calls are only possible from within the main program loop
and not from within subroutines.
Comments and additions welcome, especially for minimizing overhead in code.
Greetings, peter
'Method for program gosub using scratchpad as program stack
'scratchpad usage
' 0 = stackpointer holds 0 at reset/powerup, points to next free entry
' 1 = parent ID 1
' 2 = return ID 1
' 3 = function ID 1
' 4 = parent ID 2
' 5 = return ID 2
' 6 = function ID 2
' 7 = parent ID 3
' 8 = return ID 3
' 9 = function ID 3
'10 = parent ID 4
'11 = return ID 4
'12 = function ID 4
StackPointer var nib 'stackpointer
ParentID var nib 'parent program to return to
ReturnID var byte 'parent program entry to return to
FunctionID var byte 'function to call (255 for return, enabled by function)
'Program 0
ProgramID con 0
begin:
get 0,StackPointer
if StackPointer<>0 then bg0 'SP set so must be call from other program
StackPointer=1 'initialize stack
put 0,StackPointer
goto main0 'enter mainloop
bg0:
get StackPointer-1,FunctionID 'get FunctionID
if FunctionID=255 then bg1 'if 255 then return address
branch FunctionID,[noparse][[/noparse]function0,function1,...]
bg1:
get StackPointer-2,ReturnID
branch ReturnID,[noparse][[/noparse]return0,return1,...]
main0:
' ...
put StackPointer,ProgramID
put StackPointer+1,0
put StackPointer+2,14
StackPointer=StackPointer+3
put 0,StackPointer
run 4 'execute function 14 in program 4, then return to return0
return0:
StackPointer=StackPointer-3
put 0,StackPointer
' ...
put StackPointer,ProgramID
put StackPointer+1,1
put StackPointer+2,167
StackPointer=StackPointer+3
put 0,StackPointer
run 7 'excute function 167 in program 7, then return to return1
return1:
StackPointer=StackPointer-3
put 0,StackPointer
' ...
main0end:
goto main
'function entries for program calls
function0:
gosub function_0
goto ReturnFromProgramCall
function1:
gosub function_1
goto ReturnFromProgramCall
' ...
ReturnFromProgramCall:
get StackPointer-3,ParentID 'get ParentID
put StackPointer-1,255 'enable return entry
run ParentID 'return to parent program
'note that StackPointer-3 could hold a return value
'as it is not used by the return branch
'subroutines
function_0:
return
function_1:
return
end
'Program 1
ProgramID con 1
'enter begin section for program 1
'etc.
Adding a simple stack mechanism allows for program calling instead of
jumping.
Both return points and function entries are handled in the same manner.
The setup described below allows for 256 return entries and 255 functions
per program.
By using a nibble for the stackpointer up to 4 nested program calls are
possible.
Utilizes 3 scratchpad bytes per call and just 4 variables.
Program calls are only possible from within the main program loop
and not from within subroutines.
Comments and additions welcome, especially for minimizing overhead in code.
Greetings, peter
'Method for program gosub using scratchpad as program stack
'scratchpad usage
' 0 = stackpointer holds 0 at reset/powerup, points to next free entry
' 1 = parent ID 1
' 2 = return ID 1
' 3 = function ID 1
' 4 = parent ID 2
' 5 = return ID 2
' 6 = function ID 2
' 7 = parent ID 3
' 8 = return ID 3
' 9 = function ID 3
'10 = parent ID 4
'11 = return ID 4
'12 = function ID 4
StackPointer var nib 'stackpointer
ParentID var nib 'parent program to return to
ReturnID var byte 'parent program entry to return to
FunctionID var byte 'function to call (255 for return, enabled by function)
'Program 0
ProgramID con 0
begin:
get 0,StackPointer
if StackPointer<>0 then bg0 'SP set so must be call from other program
StackPointer=1 'initialize stack
put 0,StackPointer
goto main0 'enter mainloop
bg0:
get StackPointer-1,FunctionID 'get FunctionID
if FunctionID=255 then bg1 'if 255 then return address
branch FunctionID,[noparse][[/noparse]function0,function1,...]
bg1:
get StackPointer-2,ReturnID
branch ReturnID,[noparse][[/noparse]return0,return1,...]
main0:
' ...
put StackPointer,ProgramID
put StackPointer+1,0
put StackPointer+2,14
StackPointer=StackPointer+3
put 0,StackPointer
run 4 'execute function 14 in program 4, then return to return0
return0:
StackPointer=StackPointer-3
put 0,StackPointer
' ...
put StackPointer,ProgramID
put StackPointer+1,1
put StackPointer+2,167
StackPointer=StackPointer+3
put 0,StackPointer
run 7 'excute function 167 in program 7, then return to return1
return1:
StackPointer=StackPointer-3
put 0,StackPointer
' ...
main0end:
goto main
'function entries for program calls
function0:
gosub function_0
goto ReturnFromProgramCall
function1:
gosub function_1
goto ReturnFromProgramCall
' ...
ReturnFromProgramCall:
get StackPointer-3,ParentID 'get ParentID
put StackPointer-1,255 'enable return entry
run ParentID 'return to parent program
'note that StackPointer-3 could hold a return value
'as it is not used by the return branch
'subroutines
function_0:
return
function_1:
return
end
'Program 1
ProgramID con 1
'enter begin section for program 1
'etc.
Comments
Neat programming! You asked for comments, suggestions, so here goes.
Could you dispense with the separate variables, returnID and
functionID? The code as written has to parse both the functionID and
the returnID before getting down to business. The RUN command is
slow enough as it is!
I would suggest using one variable, say, targetID. Each bank can
then have up to 256 targets which can be either function entry ids or
return ids. (That is surely enough in combination for one Basic
stamp bank!). That would reduce the stack to 2 bytes per call
instead of 3.
In this scheme the calling program puts the parentID and returnID on
the stack, and then puts the desired function entry id in the
targetid variable and finally RUNs the desired bank. On return, the
function needs to pull the returnID off the stack, into variable,
targetID, and run the bank specified in parentID. At the top of the
bank, it only needs to BRANCH on targetID.
I would add the caveat that FOR-NEXT logic as well as GOSUB logic are
zapped by the cross-bank calls. The calls cannot be made from within
either of these structures.
-- best regards
Tracy Allen
electronically monitored ecosystems
http://www.emesystems.com/BS2SX.htm <-- another take on the x-bank
issue, using a single level "stack" and limited to 16 function/return
target points.
mailto:tracy@e...
peterverkaik@b... wrote:
>Adding a simple stack mechanism allows for program calling instead of
>jumping.
>Both return points and function entries are handled in the same manner.
>The setup described below allows for 256 return entries and 255 functions
>per program.
>By using a nibble for the stackpointer up to 4 nested program calls are
>possible.
>Utilizes 3 scratchpad bytes per call and just 4 variables.
>Program calls are only possible from within the main program loop
>and not from within subroutines.
>
>Comments and additions welcome, especially for minimizing overhead in code.
>
>
>Greetings, peter
>
>
>'Method for program gosub using scratchpad as program stack
>
>'scratchpad usage
>' 0 = stackpointer holds 0 at reset/powerup, points to next free entry
>' 1 = parent ID 1
>' 2 = return ID 1
>' 3 = function ID 1
>' 4 = parent ID 2
>' 5 = return ID 2
>' 6 = function ID 2
>' 7 = parent ID 3
>' 8 = return ID 3
>' 9 = function ID 3
>'10 = parent ID 4
>'11 = return ID 4
>'12 = function ID 4
>
>
>StackPointer var nib 'stackpointer
>ParentID var nib 'parent program to return to
>ReturnID var byte 'parent program entry to return to
>FunctionID var byte 'function to call (255 for
>return, enabled by function)
>
>
>'Program 0
>
>ProgramID con 0
>
>begin:
> get 0,StackPointer
> if StackPointer<>0 then bg0 'SP set so must be call from
>other program
> StackPointer=1 'initialize stack
> put 0,StackPointer
> goto main0 'enter mainloop
>bg0:
> get StackPointer-1,FunctionID 'get FunctionID
> if FunctionID=255 then bg1 'if 255 then return address
> branch FunctionID,[noparse][[/noparse]function0,function1,...]
>bg1:
> get StackPointer-2,ReturnID
> branch ReturnID,[noparse][[/noparse]return0,return1,...]
>
>main0:
>
>' ...
>
> put StackPointer,ProgramID
> put StackPointer+1,0
> put StackPointer+2,14
> StackPointer=StackPointer+3
> put 0,StackPointer
> run 4 'execute function 14 in program 4, then
>return to return0
>return0:
> StackPointer=StackPointer-3
> put 0,StackPointer
>
>' ...
>
> put StackPointer,ProgramID
> put StackPointer+1,1
> put StackPointer+2,167
> StackPointer=StackPointer+3
> put 0,StackPointer
> run 7 'excute function 167 in program 7, then
>return to return1
>return1:
> StackPointer=StackPointer-3
> put 0,StackPointer
>
>' ...
>
>main0end:
> goto main
>
>'function entries for program calls
>
>function0:
> gosub function_0
> goto ReturnFromProgramCall
>
>function1:
> gosub function_1
> goto ReturnFromProgramCall
>
>' ...
>
>ReturnFromProgramCall:
> get StackPointer-3,ParentID 'get ParentID
> put StackPointer-1,255 'enable return entry
> run ParentID 'return to parent program
> 'note that
>StackPointer-3 could hold a return value
> 'as it is not used by
>the return branch
>'subroutines
>
>function_0:
> return
>
>function_1:
> return
>
>end
>
>'Program 1
>
>ProgramID con 1
>
>'enter begin section for program 1
>'etc.
>
Thanks for your response.
Starting with your second remark: You're right.
Calling is only possible at the main level OUTSIDE loops.
The first remark concerning a single id for function and return is tricky
as the returnid and functionid are both required to call a function, while
when
the function ends only the returnid is required.
At the start of each program we need to know wether we are calling a
function or
returning from a call, hence the 255 to identify a return.
It would be possible to put the parentid and returnid into one byte though
parentreturn = (programid<<5)+returnid
which then allows for 32 calls to 255 functions per program
and occupies then also just 2 stack bytes.
It is a trade off: extracting the returnid from the parentreturn also
requires code,
as does a third byte on the stack. And as you say: the run command takes a
LONG
time so the extra time does not make such a difference.
Using nibbles for parentid and returnid we don't have to extract the
returnid but limits us to just 16 calls. Might be wnough.
By using 8-15 for the parentid we could use that to identify a return and
have returnflags.
Bit3 of parentid identifies call/return, bits 2-0 state the parentid
(bit3=0) or returnflags (bit3=1).
Any bytevalue can then be returned via the functionid.
If interested I could adapt the code to reflect the nibble approach.
Putting everything into one byte could also be done
target = (programid<<5)+(returnid<<3)+functionid
which allows for 4 calls to 8 fuctions across 8 programs
or
target = (programid<<6)+(returnid<<3)+functionid
which allows for 8 calls to 8 functions across 4 programs
or
target = (programid<<7)+(returnid<<3)+functionid
which allows for 16 calls to 8 functions across 2 programs
or
target=(returnid<<4)+functionid
which allows for 16 calls to 16 functions in one deicated program
All the onebyte solutions do not appeal to me considering
the overhead and the limited use.
More suggestions welcome.
Greetings, peter
Applying your suggestion and enhanced return values:
Allowing for 7 nested program calls,
256 calls to 256 functions per program
Added code to distinguis between program call and direct run
Comments and additions welcome, especially for minimizing overhead in code.
Greetings, peter
'Method for program gosub using scratchpad as program stack
'scratchpad usage
' 0 = stackpointer holds 0 at reset/powerup, points to next free entry
' 1 = parent ID 1
' 2 = return ID 1
' 3 = parent ID 2
' 4 = return ID 2
' 5 = parent ID 3
' 6 = return ID 3
' 7 = parent ID 4
' 8 = return ID 4
' 9 = parent ID 5
'10 = return ID 5
'11 = parent ID 6
'12 = return ID 6
'13 = parent ID 7
'14 = return ID 7
'all variables must be the same in all programs
StackPointer var nib 'stackpointer
ParentID var byte 'parent program to return to, return flags
ReturnID var byte 'parent program entry to return to
FunctionID var byte 'function to call, return value
RunDirect var bit '0 for program call, 1 for direct run
'Program 0
ProgramID con 0
begin:
if RunDirect then main0 'check for run direct
get 0,StackPointer
if StackPointer<>0 then bg0 'SP set so must be call from other program
StackPointer=1 'initialize stack
put 0,StackPointer
goto main0 'enter mainloop
bg0:
get StackPointer-2,ParentID 'get ParentID
if ParentID>128 then bg1 'if > 128 then return address
branch FunctionID,[noparse][[/noparse]function0,function1,...]
bg1:
get StackPointer-1,ReturnID
branch ReturnID,[noparse][[/noparse]return0,return1,...]
main0:
' ...
put StackPointer,ProgramID
put StackPointer+1,0
FunctionID=14
StackPointer=StackPointer+2
put 0,StackPointer
run 4 'execute function 14 in program 4, then return to return0
return0:
StackPointer=StackPointer-2
put 0,StackPointer
if (parentID & 127) <> 0 then ... 'check flags
if functionID<>0 then ... 'check return value
' ...
put StackPointer,ProgramID
put StackPointer+1,1
FunctionID=167
StackPointer=StackPointer+2
put 0,StackPointer
run 7 'excute function 167 in program 7, then return to return1
return1:
StackPointer=StackPointer-2
put 0,StackPointer
if (parentID & 127) <> 0 then ... 'check flags
if functionID<>0 then ... 'check return value
' ...
runDirect=1
run 3 'run program 3
main0end:
goto main
'function entries for program calls
function0:
gosub function_0
goto ReturnFromProgramCall
function1:
gosub function_1
goto ReturnFromProgramCall
' ...
ReturnFromProgramCall:
get StackPointer-2,ParentID 'get ParentID
put StackPointer-2,128+0 'enable return + add flags
FunctionID=255 'return a value
run ParentID 'return to parent program
'subroutines
function_0:
return
function_1:
return
end
'Program 1
ProgramID con 1
'enter begin section for program 1
'etc.
Errata on code line
if ParentID>128 then bg1 'if > 128 then return address
must be
if ParentID>127 then bg1 'if > 127 then return address
Greetings, peter
'Method for program gosub using scratchpad as program stack
'scratchpad usage
' 0 = stackpointer holds 0 at reset/powerup, points to next free entry
' 1 = parent ID 1
' 2 = return ID 1
' 3 = parent ID 2
' 4 = return ID 2
' 5 = parent ID 3
' 6 = return ID 3
' 7 = parent ID 4
' 8 = return ID 4
' 9 = parent ID 5
'10 = return ID 5
'11 = parent ID 6
'12 = return ID 6
'13 = parent ID 7
'14 = return ID 7
'all variables must be the same in all programs
StackPointer var nib 'stackpointer
ParentID var byte 'parent program to return to, return flags
ReturnID var byte 'parent program entry to return to
FunctionID var byte 'function to call, return value
RunDirect var bit '0 for program call, 1 for direct run
'Program 0
ProgramID con 0
begin:
if RunDirect then main0 'check for run direct
get 0,StackPointer
if StackPointer<>0 then bg0 'SP set so must be call from other program
StackPointer=1 'initialize stack
put 0,StackPointer
goto main0 'enter mainloop
bg0:
get StackPointer-2,ParentID 'get ParentID
if ParentID>127 then bg1 'if > 127 then return address
branch FunctionID,[noparse][[/noparse]function0,function1,...]
bg1:
get StackPointer-1,ReturnID
branch ReturnID,[noparse][[/noparse]return0,return1,...]
main0:
' ...
put StackPointer,ProgramID
put StackPointer+1,0
FunctionID=14
StackPointer=StackPointer+2
put 0,StackPointer
run 4 'execute function 14 in program 4, then return to return0
return0:
StackPointer=StackPointer-2
put 0,StackPointer
if (parentID & 127) <> 0 then ... 'check flags
if functionID<>0 then ... 'check return value
' ...
put StackPointer,ProgramID
put StackPointer+1,1
FunctionID=167
StackPointer=StackPointer+2
put 0,StackPointer
run 7 'excute function 167 in program 7, then return to return1
return1:
StackPointer=StackPointer-2
put 0,StackPointer
if (parentID & 127) <> 0 then ... 'check flags
if functionID<>0 then ... 'check return value
' ...
runDirect=1
run 3 'run program 3
main0end:
goto main
'function entries for program calls
function0:
gosub function_0
goto ReturnFromProgramCall
function1:
gosub function_1
goto ReturnFromProgramCall
' ...
ReturnFromProgramCall:
get StackPointer-2,ParentID 'get ParentID
put StackPointer-2,128+0 'enable return + add flags
FunctionID=255 'return a value
run ParentID 'return to parent program
'subroutines
function_0:
return
function_1:
return
end
'Program 1
ProgramID con 1
'enter begin section for program 1
'etc.
Doing some code optimization.
Any suggestions regarding parameter passing?
Greetings, peter
'Method for program gosub using scratchpad as program stack
'scratchpad usage
' 0 = stackpointer holds 0 at reset/powerup, points to next free entry
' 1 = parent ID 1
' 2 = return ID 1
' 3 = parent ID 2
' 4 = return ID 2
' 5 = parent ID 3
' 6 = return ID 3
' 7 = parent ID 4
' 8 = return ID 4
' 9 = parent ID 5
'10 = return ID 5
'11 = parent ID 6
'12 = return ID 6
'13 = parent ID 7
'14 = return ID 7
'all variables must be the same in all programs
StackPointer var nib 'stackpointer
ParentID var byte 'parent program to return to, return flags
ReturnID var byte 'parent program entry to return to
FunctionID var byte 'function to call, return value
RunDirect var bit '0 for program call, 1 for direct run
'Program 0
ProgramID con 0
begin:
if RunDirect then main0 'check for run direct
get 0,StackPointer
if StackPointer<>0 then bg0 'SP set so must be call from other program
StackPointer=1 'initialize stack
put 0,StackPointer
goto main0 'enter mainloop
bg0:
get StackPointer-2,ParentID 'get ParentID
if ParentID>127 then bg1 'if > 127 then return address
branch FunctionID,[noparse][[/noparse]function0,function1,...]
bg1:
get StackPointer-1,ReturnID
StackPointer=StackPointer-2 'as all are retrieved, adjust stack here
ParentID.bit7=0 'clear returnidflag, bits 6-0 hold returnflags
put 0,StackPointer
branch ReturnID,[noparse][[/noparse]return0,return1,...]
main0:
' ...
put StackPointer,ProgramID
put StackPointer+1,0
FunctionID=14
StackPointer=StackPointer+2
put 0,StackPointer
run 4 'execute function 14 in program 4, then return to return0
return0:
if parentID<>0 then ... 'check flags
if functionID<>0 then ... 'check return value
' ...
put StackPointer,ProgramID
put StackPointer+1,1
FunctionID=167
StackPointer=StackPointer+2
put 0,StackPointer
run 7 'excute function 167 in program 7, then return to return1
return1:
if parentID<>0 then ... 'check flags
if functionID<>0 then ... 'check return value
' ...
runDirect=1
run 3 'run program 3
main0end:
goto main
'function entries for program calls
function0:
gosub function_0
goto ReturnFromProgramCall
function1:
gosub function_1
goto ReturnFromProgramCall
' ...
ReturnFromProgramCall:
get StackPointer-2,ParentID 'get ParentID
put StackPointer-2,128+0 'enable return + add flags
FunctionID=255 'return a value
run ParentID 'return to parent program
'subroutines
function_0:
return
function_1:
return
end
'Program 1
ProgramID con 1
'enter begin section for program 1
'etc.
Redefining variables allows for 15-bit return values.
Greetings, peter
'Method for program gosub using scratchpad as program stack
'scratchpad usage
' 0 = stackpointer holds 0 at reset/powerup, points to next free entry
' 1 = parent ID 1
' 2 = return ID 1
' 3 = parent ID 2
' 4 = return ID 2
' 5 = parent ID 3
' 6 = return ID 3
' 7 = parent ID 4
' 8 = return ID 4
' 9 = parent ID 5
'10 = return ID 5
'11 = parent ID 6
'12 = return ID 6
'13 = parent ID 7
'14 = return ID 7
'all variables must be the same in all programs
StackPointer var nib 'stackpointer
TargetID var word
ParentID var TargetID.highbyte 'parent program to return to, returnflags
FunctionID var TargetID.lowbyte 'function to call, returnvalue
ReturnID var byte 'parent program entry to return to
RunDirect var bit '0 for program call, 1 for direct run
'Program 0
ProgramID con 0
begin:
if RunDirect then main0 'check for run direct
get 0,StackPointer
if StackPointer<>0 then bg0 'SP set so must be call from other program
StackPointer=1 'initialize stack
put 0,StackPointer
goto main0 'enter mainloop
bg0:
get StackPointer-2,ParentID 'get ParentID
if ParentID>127 then bg1 'if > 127 then return address
branch FunctionID,[noparse][[/noparse]function0,function1,...]
bg1:
get StackPointer-1,ReturnID
StackPointer=StackPointer-2 'as all are retrieved, adjust stack here
ParentID.bit7=0 'clear returnidflag, bits 6-0 hold returnflags
put 0,StackPointer
branch ReturnID,[noparse][[/noparse]return0,return1,...]
main0:
' ...
put StackPointer,ProgramID
put StackPointer+1,0
FunctionID=14
StackPointer=StackPointer+2
put 0,StackPointer
run 4 'execute function 14 in program 4, then return to return0
return0:
if TargetID<>0 then ... 'check 15-bit return value
' ...
put StackPointer,ProgramID
put StackPointer+1,1
FunctionID=167
StackPointer=StackPointer+2
put 0,StackPointer
run 7 'excute function 167 in program 7, then return to return1
return1:
if parentID<>0 then ... 'check return flags
if functionID<>0 then ... 'check return value
' ...
runDirect=1
run 3 'run program 3
main0end:
goto main
'function entries for program calls
function0:
gosub function_0
goto ReturnFromProgramCall
function1:
gosub function_1
goto ReturnFromProgramCall
' ...
ReturnFromProgramCall:
get StackPointer-2,ParentID 'get ParentID
put StackPointer-2,128+0 'enable return + add flags
FunctionID=255 'return a value
run ParentID 'return to parent program
'subroutines
function_0:
return
function_1:
return
end
'Program 1
ProgramID con 1
'enter begin section for program 1
'etc.
Cleaning up stackpointer use.
Stackpointer now points to last used byte, no copy saved on scratchpad
location 0
Greetings, peter
'Method for program gosub using scratchpad as program stack
'scratchpad usage
' 0 = not used
' 1 = parent ID 1
' 2 = return ID 1
' 3 = parent ID 2
' 4 = return ID 2
' 5 = parent ID 3
' 6 = return ID 3
' 7 = parent ID 4
' 8 = return ID 4
' 9 = parent ID 5
'10 = return ID 5
'11 = parent ID 6
'12 = return ID 6
'13 = parent ID 7
'14 = return ID 7
'all variables must be the same in all programs
StackPointer var nib 'stackpointer
TargetID var word
ParentID var TargetID.highbyte 'parent program to return to, returnflags
FunctionID var TargetID.lowbyte 'function to call, returnvalue
ReturnID var byte 'parent program entry to return to
RunDirect var bit '0 for program call, 1 for direct run
'Program 0
ProgramID con 0
begin:
if RunDirect then main0 'check for run direct
if StackPointer=0 then main0 'SP clear so no program call/return
get StackPointer-1,ParentID 'get ParentID
if ParentID.bit7 then bg1 'if > 127 then return address
branch FunctionID,[noparse][[/noparse]function0,function1,...]
bg1:
get StackPointer,ReturnID
StackPointer=StackPointer-2 'as all are retrieved, adjust stack here
ParentID.bit7=0 'clear returnidflag, bits 6-0 hold returnflags
branch ReturnID,[noparse][[/noparse]return0,return1,...]
main0:
' ...
put StackPointer+1,ProgramID
put StackPointer+2,0
FunctionID=14
StackPointer=StackPointer+2
run 4 'execute function 14 in program 4, then return to return0
return0:
if TargetID<>0 then ... 'check 15-bit return value
' ...
put StackPointer+1,ProgramID
put StackPointer+2,1
FunctionID=167
StackPointer=StackPointer+2
run 7 'execute function 167 in program 7, then return to return1
return1:
if parentID<>0 then ... 'check return flags
if functionID<>0 then ... 'check return value
' ...
runDirect=1
run 3 'run program 3
main0end:
goto main
'function entries for program calls
function0:
gosub function_0
goto ReturnFromProgramCall
function1:
gosub function_1
goto ReturnFromProgramCall
' ...
ReturnFromProgramCall:
get StackPointer-1,ParentID 'get ParentID
put StackPointer-1,128+0 'enable return + add flags
FunctionID=0 'return a value
run ParentID 'return to parent program
'subroutines
function_0:
return
function_1:
return
end
'Program 1
ProgramID con 1
'enter begin section for program 1
'etc.
I am not certain what you mean but I assume you wonder what the benefit is
to have a multilevel stack.
Consider a function entry like entry03.
In this example it just consists of a single gosub.
For a real application it might be necessary to have multiple gosubs
inside a function, including program calls. For that purpose the answer
to your question is yes: you've got to keep track.
Obviously, if you have enough program space you can duplicate functions
to other programs thereby avoiding program calls and use gosubs instead.
Greetings, peter
Two adjustments.
First, I redesigned the stack to grow downwards into the scratchpad.
This way the scratchpad is available from 0 to TOS+1-Stackpointer.
Now TOS+1-stackpointer can be used as the maximum number of bytes to read
into
the scratchpad using SERIN without causing any stack conflict.
I discarded the one bit return value and now use bit3 of TargetID as an
extra bit
for entryID. As the parameters used when doing a program call are constants
this
causes no extra overhead, the constants are just defined differently, but it
allows
for 32 entries while maintaining a single branch list.
Greetings, peter
Any more suggestions are welcome.
'Method for program gosub using scratchpad as program stack
'scratchpad usage (TOS stands for Top Of Stack)
'TOS-0 = entryID/runID 1
'TOS-1 = entryID/runID 2
'TOS-2 = entryID/runID 3
'etc.
'program 0
TopOfStack1 con 62 'top of stack fot BSX
TopOfStack2 con 126 'top of stack for BSE, BSP
TOS con TopOfStack2 'pick the right top of stack
'global variables
TargetID var byte 'entry index and program to run
runID var TargetID.lownib 'entry index b0, program to run
entryID var TargetID.highnib 'entry index b4-b1
StackPointer var nib 'stackpointer, use byte for deeper stack
ProgramID con 0
Begin0:
branch (TargetID>>3),[noparse][[/noparse]main0,entry01,...,entry1F] 'first entry for direct
run
main0:
'init
main0loop:
' ...
put TOS-StackPointer,$08+ProgramID 'constant: entryID in b7-b3, runID in
b2-b0
StackPointer=StackPointer-1
TargetID=$74 'constant: entryID in highnib, runID in lownib
run TargetID 'execute entry 14 in program 4, then return to entry01
entry01:
' ...
put TOS-StackPointer,$10+ProgramID 'constant
StackPointer=StackPointer-1
TargetID=$37 'constant
run TargetID 'execute entry 6 in program 7, then return to entry02
entry02:
' ...
TargetID=$03
Run TargetID 'run program 3 directly (no call but jump)
main0end:
goto main0loop
'function entries for program calls
entry03:
gosub function_03
goto ReturnFromProgramCall_0
entry04:
gosub function_04
goto ReturnFromProgramCall_0
' ...
ReturnFromProgramCall_0:
StackPointer=StackPointer+1 'adjust stack here
get TOS-StackPointer,TargetID 'get TargetID
run TargetID 'return to parent program
'subroutines
function_03:
return
function_04:
return
end
'Program 1
'etc.
Apparently I changed too many +/- signs concerning stack control.
I have rectified this.
Greetings, peter
Any more suggestions are welcome.
'Method for program gosub using scratchpad as program stack
'scratchpad usage (TOS stands for Top Of Stack)
'TOS-0 = entryID/runID 1
'TOS-1 = entryID/runID 2
'TOS-2 = entryID/runID 3
'etc.
'program 0
TopOfStack1 con 62 'top of stack fot BSX
TopOfStack2 con 126 'top of stack for BSE, BSP
TOS con TopOfStack2 'pick the right top of stack
'global variables
TargetID var byte 'entry index and program to run
runID var TargetID.lownib 'entry index b0, program to run
entryID var TargetID.highnib 'entry index b4-b1
StackPointer var nib 'stackpointer, use byte for deeper stack
ProgramID con 0
Begin0:
branch (TargetID>>3),[noparse][[/noparse]main0,entry01,...,entry1F] 'first entry for direct
run
main0:
'init
main0loop:
' ...
put TOS-StackPointer,$08+ProgramID 'constant: entryID in b7-b3, runID in
b2-b0
StackPointer=StackPointer+1
TargetID=$74 'constant: entryID in highnib, runID in lownib
run TargetID 'execute entry 14 in program 4, then return to entry01
entry01:
' ...
put TOS-StackPointer,$10+ProgramID 'constant
StackPointer=StackPointer+1
TargetID=$37 'constant
run TargetID 'execute entry 6 in program 7, then return to entry02
entry02:
' ...
TargetID=$03
Run TargetID 'run program 3 directly (no call but jump)
main0end:
goto main0loop
'function entries for program calls
entry03:
gosub function_03
goto ReturnFromProgramCall_0
entry04:
gosub function_04
goto ReturnFromProgramCall_0
' ...
ReturnFromProgramCall_0:
StackPointer=StackPointer-1 'adjust stack here
get TOS-StackPointer,TargetID 'get TargetID
run TargetID 'return to parent program
'subroutines
function_03:
return
function_04:
return
end
'Program 1
'etc.
Thanks for your suggestions.
I have put them in, together with some readable constants for the TargetID
values.
Now I can use stackpointer as the max. number of bytes read in with SERIN to
avoid conflicts.
Your suggestion regarding the stack overrun/underrun would keep the
stackpointer inside
Its boundaries but then the stack might get corrupted, so where does that
leave me?
As I see it, the only way to be sure there are no stack overrun errors is to
check
before each call wether the stackpointer has reached the BOS. If it does, do
not perform the
call. At this point you then have options to debug a message or lower the
BOS (only if you
place the BOS in a variable) or just reset (jump to main0). If there are no
parameters
placed onto the stack, then each call places one byte onto the stack,
equivalent to a gosub.
For the gosubs the compiler issues a message when the nesting goes too deep,
for the program
calls you must manually keep track of the nesting level and select stacksize
to match.
Finally, the parameter passing.
Suppose I have a function that takes 2 variables.
Then I would get some code as below:
'program 0
var x byte 'local vars
var y byte
put StackPointer,x 'first put parameters onto stack
put StackPointer-1,y
put StackPointer-2,EN1P0 'then put return point
StackPointer=StackPointer-3
TargetID=EN14P4
run TargetID 'execute entry 14 in program 4, then return to entry01
entry01:
get StackPointer+2,x 'retrieve results
get StackPointer+1,y
StackPointer=StackPointer+2 'remove results from stack
' ...
'program 4
var x byte 'local vars
var y byte
entry14:
get StackPointer+3,x 'get parameters from stack
get StackPointer+2,y
gosub function_14 'perform some math on x and y, results in x and y
put StackPointer+3,x 'place results on stack
put StackPointer+2,y
goto ReturnFromProgramCall_4
As you can see the code is quite clear although bulky. I don't have to
shuffle entry
points but put them on last. The called function removes this from the stack
upon return but leaves the pushed parameters (that may hold results) on it.
Just pop them off and adjust the stackpointer. Although there is a lot of
code
it provides a powerfull mechanism.
This mechanism can also provide me with EXTRA NAMED VARIABLES to break the
26 byte barrier.
(not really extra, I trade scratchpad space for named variable space).
Consider this:
We have defined say 16 bytes of ram as global variables (definition and use
equal to all
programs) and the remaining 10 bytes of ram as local variables (definition
and use dedicated
per program, changing a value in one program does not alter the variable
that occupies the
same ram location in an other program).
Say I have two subroutines PushLocal and PopLocal that place and remove the
last 10 bytes
of ram.
gosub PushLocal 'save last 10 ram bytes onto stack
put StackPointer,EN1P0
StackPointer=StackPointer-1
TargetID=EN14P4
run TargetID 'execute entry 14 in program 4, then return to entry01
entry01:
gosub PopLocal 'restore last 10 ram bytes
' ...
PushLocal:
put StackPointer,B16
put StackPointer-1,B17
put StackPointer-2,B18
put StackPointer-3,B19
put StackPointer-4,B20
put StackPointer-5,B21
put StackPointer-6,B22
put StackPointer-7,B23
put StackPointer-8,B24
put StackPointer-9,B25
StackPointer=StackPointer-10
return
PopLocal:
get StackPointer+1,B25
get StackPointer+2,B24
get StackPointer+3,B23
get StackPointer+4,B22
get StackPointer+5,B21
get StackPointer+6,B20
get StackPointer+7,B19
get StackPointer+8,B18
get StackPointer+9,B17
get StackPointer+10,B16
StackPointer=StackPointer+10
return
Although this makes calls even slower, it does give me more named variables
and is transparent
to other (nested) program calls. I could have a function call itself this
way. The local
variables make it then re-entrant.
Variable definition have to be very precise (use predefined names like W0
to declare variables at a known location).
I hope this demonstrates the power that a multilevel stack can provide.
Greetings, peter
Any more suggestions are welcome.
'Method for program gosub using scratchpad as program stack
'scratchpad usage (TOS stands for Top Of Stack)
'TOS-0 = entryID/runID 1
'TOS-1 = entryID/runID 2
'TOS-2 = entryID/runID 3
'etc.
'program 0
TopOfStack1 con 62 'top of stack fot BSX, BSE
TopOfStack2 con 126 'top of stack for BSP
TOS con TopOfStack2 'pick the right top of stack
STACKSIZE con 30 'define stacksize
BOS con TOS-STACKSIZE 'bottom of stack
EN1P0 con (1<<3)+0 'entry01 in program 0
EN2P0 con (2<<3)+0 'entry02 in program 0
EN14P4 con (14<<3)+4 'entry 14 in program 4
EN6P7 con (6<<3)+7 'entry 6 in program 7
'global variables
TargetID var byte 'entry index and program to run
runID var TargetID.lownib 'entry index b0, program to run
entryID var TargetID.highnib 'entry index b4-b1
StackPointer var byte 'stackpointer
ProgramID con 0
Begin0:
branch (TargetID>>3),[noparse][[/noparse]main0,entry01,...,entry1F] 'first entry for direct
run
main0:
StackPointer=TOS
'init
main0loop:
' ...
put StackPointer,EN1P0
StackPointer=StackPointer-1
TargetID=EN14P4
run TargetID 'execute entry 14 in program 4, then return to entry01
entry01:
' ...
put StackPointer,EN2P0
StackPointer=StackPointer-1
TargetID=EN6P7
run TargetID 'execute entry 6 in program 7, then return to entry02
entry02:
' ...
TargetID=$03
Run TargetID 'run program 3 directly (no call but jump)
main0end:
goto main0loop
'function entries for program calls
entry03:
gosub function_03
goto ReturnFromProgramCall_0
entry04:
gosub function_04
goto ReturnFromProgramCall_0
' ...
ReturnFromProgramCall_0:
StackPointer=StackPointer+1 'adjust stack here
get StackPointer,TargetID 'get TargetID
run TargetID 'return to parent program
'subroutines
function_03:
return
function_04:
return
end
'Program 1
'etc.
Here is a supplement to my last message regarding local variables.
I intend to use the stack mechanism for gosubs as well, to finally
get rid of some obscure errors that I frequently encounter due to lack
of variable space. And then, after tens of minutes (hours even!), to find
out that a variable that I believed was free for use, would hold a needed
value for some other part of the program. The former routines pushlocal and
poplocal have I renamed to alloc and free (as in the C language) which I
think
are more appropiate.
'global variables: allocated from B0 downwards
'local variables: allocated from B25 upwards
SomeGosub:
gosub alloc4 'allocate 4 bytes for local variables
var x W12 'local word variable
var y B23 'local byte variable
'code for this gosub routine
goto free4 'free local variables and return from gosub
alloc8: put StackPointer,B18 'space is allocated from B25 upwards
put StackPointer-1,B19
put StackPointer-2,B20
put StackPointer-3,B21
StackPointer=StackPointer-4
alloc4: put StackPointer,B22
put StackPointer-1,B23
put StackPointer-2,B24
put StackPointer-3,B25
StackPointer=StackPointer-4
return
free8: get StackPointer+8,B18
get StackPointer+7,B19
get StackPointer+6,B20
get StackPointer+5,B21
get StackPointer+4,B22
get StackPointer+3,B23
get StackPointer+2,B24
get StackPointer+1,B25
StackPointer=StackPointer+8
return
free4: get StackPointer+4,B22
get StackPointer+3,B23
get StackPointer+2,B24
get StackPointer+1,B25
StackPointer=StackPointer+4
return
I have split the alloc into alloc8 and alloc4 (8 bytes for locals is
problably enough)
so for less space I gain some speed. Notice the separate routines for free4
and free8,
there is no easy way I could figure out to combine these as in the alloc
subroutine.
The intended uses for the local (temporary) variables are for-next loops,
counters
and to hold intermediate results. Note the declaration of the local vars as
they use
predefined names to place a variable at a known location.
I do realize this makes gosubs slower but for a gosub with a lot of code
(which often
also use a lot of temporary variables, which creates the variable space
problem) it does
not matter that much as the subroutine itself may already take a long time.
The loss of speed does not outweigh the benefit not to have worry about
variable use
and possible subsequent errors.
Note that alloc and free entries are paired, if you use alloc4 you must also
use free4,
if you use alloc8 you must also use free8.
Greetings, peter
Any more suggestions are welcome.
[noparse][[/noparse]basicstamps] re: program calling with BS2e, BS2sx, BS2p
At 10:04 PM +0200 5/11/01, Peter Verkaik wrote:
>Thanks for your suggestions. I have put them in, together
>with some readable constants for the TargetID values...
>EN1P0 con (1<<3)+0 'entry 1 in program 0
>EN2P0 con (2<<3)+0 'entry 2 in program 0
>EN14P4 con (14<<3)+4 'entry 14 in program 4
>EN6P7 con (6<<3)+7 'entry 6 in program 7
I like that--it remains clear for the sake of program documentation.
>Your suggestion regarding the stack overrun/underrun would keep the
>stackpointer inside. Its boundaries but then the stack might get
>corrupted, so where does that leave me?
> ref: http://groups.yahoo.com/group/basicstamps/message/11794
It was a bad suggestion. A corrupt stack is going to be a sad face
anyway, so there is no point in clamping the overrun/underrun.
Right, it is up to the user to keep track of the stack nesting.
>Finally, the parameter passing.
>Suppose I have a function that takes 2 variables.
>Then I would get some code as below:....
> ref: http://groups.yahoo.com/group/basicstamps/message/11815
Great! It does take a lot of overhead in time and code, but it is
very systematic. What recursive function are you planning?
The code for the multi-byte push/pop could be shortened by use of a
FOR-NEXT loop:
PushLocal:
for X=0 to 9
put stackpointer-X,B16(X) ' implied array
next
stackPointer=StackPointer-10
return
PopLocal:
for X=1 to 10
get stackpointer+X,B16(10-X) ' implied array
next
stackPointer=stackPointer+10
return
>Variable definition have to be very precise (use predefined names like W0
>to declare variables at a known location).
That is another subject! I prefer _not_ to use the fixed names
(w0,..,w13; b0,..b25, nothing for nibs nor bits). Instead, I name
global _word_ variables in every bank. Being words, the compiler
puts them first in the memory map. I then name my global bytes, nibs
and bits as chunks within those words. For example,
sxword var word
sxgo var sxword.byte0
sxaux var sxword.byte1.nib0
sxflag0 var sxword.byte1.nib1.bit0
The names are aliases. The global words and chunks thereof appear in
every bank.
<http://www.emesystems.com/BS2SX.htm#variables>
There is (hopefully) some main RAM left over in the memory map after
the global variables. I use that for local variables, named with
meaningful names as usual, and the compiler will position them
automatically above my global variables no worries. I just think it
comes out more readable and maintainable that way.
The stack mechanism would in any case provide a good way to store up
all of those global variables if the entire RAM was needed for some
completely different purpose. For example, I had one application
reading out the packets of data from an Oregon Scientific weather
station, and the only way to capture all of the data was to free up
the entire ram. So I had to "stack" my main variables in scratchpad
while the BS2sx program jumped to another bank to capture the packets
of data. I didn't use a real stack for that, just an ad-hoc buffer.
(Now, the BS2p offers the SERSTR modifier that allows data like that
to be captured directly to the scratchpad RAM).
-- regards,
Tracy Allen
electronically monitored ecosystems
mailto:tracy@e...
http://www.emesystems.com