mirror of
https://github.com/irmen/prog8.git
synced 2024-11-03 13:07:54 +00:00
120 lines
5.4 KiB
ReStructuredText
120 lines
5.4 KiB
ReStructuredText
=================
|
|
Technical details
|
|
=================
|
|
|
|
All variables are static in memory
|
|
----------------------------------
|
|
|
|
All variables are allocated statically, there is no concept of dynamic heap or stack frames.
|
|
Essentially all variables are global (but scoped) and can be accessed and modified anywhere,
|
|
but care should be taken ofcourse to avoid unexpected side effects.
|
|
|
|
Especially when you're dealing with interrupts or re-entrant routines: don't modify variables
|
|
that you not own or else you will break stuff.
|
|
|
|
|
|
Software stack for expression evaluation
|
|
----------------------------------------
|
|
|
|
Prog8 uses a software stack to evaluate complex expressions that it can't calculate in-place or
|
|
directly into the target variable, register, or memory location.
|
|
|
|
'software stack' means: seperated and not using the processor's hardware stack.
|
|
|
|
The software stack is implemented as follows:
|
|
|
|
- 2 pages of memory are allocated for this, exact locations vary per machine target.
|
|
For the C-64 they are set at $ce00 and $cf00 (so $ce00-$cfff is reserved).
|
|
For the Commander X16 they are set at $0400 and $0500 (so $0400-$05ff are reserved).
|
|
- these are the high and low bytes of the values on the stack (it's a 'split 16 bit word stack')
|
|
- for byte values just the lsb page is used, for word values both pages
|
|
- float values (5 bytes) are chopped up into 2 words and 1 byte on this stack.
|
|
- the X register is permanently allocated to be the stack pointer in the software stack.
|
|
- you can use the X register as long as you're not using the software stack.
|
|
But you *must* make sure it is saved and restored after the code that modifies it,
|
|
otherwise the evaluation stack gets corrupted.
|
|
|
|
Subroutine Calling Convention
|
|
-----------------------------
|
|
|
|
Calling a subroutine requires three steps:
|
|
|
|
#. preparing the arguments (if any) and passing them to the routine
|
|
#. calling the routine
|
|
#. preparig the return value (if any) and returning that from the call.
|
|
|
|
|
|
Calling the routine is just a simple JSR instruction, but the other two work like this:
|
|
|
|
|
|
``asmsub`` routines
|
|
^^^^^^^^^^^^^^^^^^^
|
|
|
|
These are usually declarations of kernal (ROM) routines or low-level assembly only routines,
|
|
that have their arguments solely passed into specific registers.
|
|
Sometimes even via a processor status flag such as the Carry flag.
|
|
Return values also via designated registers.
|
|
The processor status flag is preserved on returning so you can immediately act on that for instance
|
|
via a special branch instruction such as ``if_z`` or ``if_cs`` etc.
|
|
|
|
|
|
regular subroutines
|
|
^^^^^^^^^^^^^^^^^^^
|
|
|
|
- subroutine parameters are just variables scoped to the subroutine.
|
|
- the arguments passed in a call are evaluated (using the eval-stack if needed) and then
|
|
copied into those variables.
|
|
This sometimes can seem inefficient but it's required to allow subroutines to work locally
|
|
with their parameters and allow them to modify them as required, without changing the
|
|
variables used in the call's arguments. If you want to get rid of this overhead you'll
|
|
have to make an ``asmsub`` routine in assembly instead.
|
|
- the return value is passed back to the caller via cpu register(s):
|
|
Byte values will be put in ``A`` .
|
|
Word values will be put in ``A`` + ``Y`` register pair.
|
|
Float values will be put in the ``FAC1`` float 'register' (Basic allocated this somewhere in ram).
|
|
|
|
|
|
Calls to builtin functions are treated in a special way:
|
|
Generally if they have a single argument it's passed in a register or register pair.
|
|
Multiple arguments are passed like a normal subroutine, into variables.
|
|
Some builtin functions have a fully custom implementation.
|
|
|
|
|
|
The compiler will warn about routines that are called and that return a value, if you're not
|
|
doing something with that returnvalue. This can be on purpuse if you're simply not interested in it.
|
|
Use the ``void`` keyword in front of the subroutine call to get rid of the warning in that case.
|
|
|
|
|
|
The 6502 CPU's X-register: off-limits
|
|
-------------------------------------
|
|
|
|
Prog8 uses the cpu's X-register as a pointer in its internal expression evaluation stack.
|
|
When only writing code in Prog8, this is taken care of behind the scenes for you by the compiler.
|
|
However when you are including or linking with assembly routines or kernal/ROM calls that *do*
|
|
use the X register (either clobbering it internally, or using it as a parameter, or return value register),
|
|
those calls will destroy Prog8's stack pointer and this will result in invalid calculations.
|
|
|
|
You should avoid using the X register in your assembly code, or take preparations.
|
|
If you make sure that the value of the X register is preserved before calling a routine
|
|
that uses it, and restored when the routine is done, you'll be ok.
|
|
|
|
Routines that return a value in the X register can be called from Prog8 but the return value is
|
|
inaccessible unless you write a short piece of inline assembly code to deal with it yourself, such as::
|
|
|
|
ubyte returnvalue
|
|
|
|
%asm {{
|
|
stx P8ZP_SCRATCH_REG ; use 'phx/plx' if using 65c02 cpu
|
|
ldx #10
|
|
jsr routine_using_x
|
|
stx returnvalue
|
|
ldx P8ZP_SCRATCH_REG
|
|
}}
|
|
; now use 'returnvalue' variable
|
|
|
|
Prog8 also provides some help to deal with this:
|
|
|
|
- you should use a ``clobbers(X)`` specification for asmsub routines that modify the X register; the compiler will preserve it for you automatically when such a routine is called
|
|
- the ``sys.rsave()`` and ``sys.rrestore()`` routines can preserve and restore *all* registers (but this is very slow and overkill if you only need to save X)
|
|
|