I'm proposing a preprocessor that automatically allocates local variables on zero page without the risk of one subroutine stepping on another subroutine's toes. It's analogous to this sort of "compiled stack" used by some C compilers that target microcontrollers and assumes that a subroutine is not recursive or otherwise reentrant, which Jarhmander mentioned earlier and Dwedit suggested applying elsewhere. I haven't yet written any of the needed code; I'm mostly spitballing here to see if I'm on the right track.Bananmos wrote: ↑Tue May 12, 2020 7:06 am for 99.9% of all code targeting the NES you wouldn't use recursion anyway, but have all your functions use zeropage. The problem is you need to avoid collisions between different variables. But this is easily done by assuming no recursion / call-by-pointer and creating a "compiled stack" of zeropage variables. In fact, this is exactly what Microchip's PIC C compiler appears to do
First some definitions:
- A callee is a subroutine that reserves space on the static stack for its local variables.
- A caller is a subroutine that calls one or more callees and does not overwrite the static stack of any of its callees.
The preprocessor would understand these keywords in code:
- var varname, sz
Creates a symbol on this subroutine's static stack called varname, local to this subroutine. Its size is sz bytes, defaulting to 1. - calls another_sub
Marks the current subroutine as a caller of another_sub (the callee). This causes the start of the current subroutine's static stack to be placed no earlier than the end of the callee's static stack. If a jump table is used, the jump table needs to be a .proc that calls all routines inside it, and the subroutine that reads the jump table also needs to be a .proc that calls the jump table. - call another_sub
Creates a caller-callee relationship and then actually calls another_sub. Equivalent to calls another_sub followed by jsr another_sub. - tailcalls another_sub
Marks the current subroutine as a tail caller of another_sub. In a tail call, all local variables' lifetimes end before the call. This causes the start of the current subroutine's static stack to be placed no earlier than the start of the callee's static stack. A fall-through to another subroutine should also be marked as a tail call. - tailcall another_sub
Creates a tail caller-callee relationship and actually tail calls another_sub. Equivalent to tailcalls another_sub followed by jmp another_sub. - leaf
Suppresses a warning about declaring a var but having no calls. A leaf function is a callee but not a caller, and its static stack begins at the start of static stack space. If a function declares a var but isn't marked as a caller, the preprocessor otherwise emits a warning in case the programmer forgot to use calls.