6.1 Compiled Stack Directives
The assembler directives that control the compiled stack all begin with the
letters FN
.
The FNCONF
directive is used once per program. It's three
arguments indicate the name of the psect that should be used to hold the compiled stack,
the symbol prefix to be used for auto-style objects, and the symbol prefix to be used
for parameters objects.
FNCONF udata_acs,?au_,?pa_
which indicates that the
udata_acs
psect (located in Access bank data memory) will be used
to hold all the objects on the stack. The linker will form the stack in one contiguous
chunk of memory. The location of the psect holding the stack will affect how the stack
objects must be accessed. If there are a large number of stack-based objects and
particularly if they are accessed often, placing the stack in the PIC18 Access bank will
mean they can be accessed without any bank selection instructions. If the stack becomes
too large, however, it will need to be placed in banked memory. Mid-range and Baseline
devices have little common memory, and this might be needed for other purposes, so
banked memory is often used for the compiled stack on these devices.?au_
symbol specified as the second argument to the
FNCONF
directive will be prefixed to the name of a routine to
create the base symbol for that routine's auto-style objects. The ?pa_
prefix will be used to form the base symbol for each routine's parameter objects. These
symbols are illustrated in the add
routine, which begins with the
following
code:PSECT code
;add needs 4 bytes of parameters, but no autos
FNSIZE add,0,4 ;two 2-byte parameters
GLOBAL ?pa_add
;add the two 'int' parameters, returning the result in the first parameter location
add:
movf ?pa_add+2,w,c
addwf ?pa_add+0,f,c
The
FNSIZE
directive takes three arguments, those being the name of a
routine, the total number of bytes required for that routine's auto-like objects, and
the total number of bytes for its parameter-like objects. In this case, the
FNSIZE
directive indicates that the add
routine
needs no auto-style objects and 4 bytes of parameters. The directive can be placed
anywhere in your code, but it is often located near the routine it configures.The linker will automatically create a symbol associated with the block of 4 bytes used
by the routine's parameters. In this case, that symbol will be ?pa_add
,
based on the prefix used with the FNCONF
directive. Although this
symbol is defined by the linker, it still needs to be declared in each module that needs
it. This has been done in the above code by the GLOBAL ?pa_add
directive. Each byte of the parameter memory can be accessed by using an offset from the
?pa_add
symbol. The code above shows the first and third bytes of
this memory being accessed. What these 4 bytes represent is entirely up to you. In the
example, the parameter memory is used to hold two, 2-byte-wide objects, but the same
FNSIZE
arguments could instead be used to create one, 4-byte-wide
object, for example.
main
routine.GLOBAL ?au_main
GLOBAL result
result EQU ?au_main+0 ;create an alias for this auto location
GLOBAL incval
incval EQU ?au_main+2 ;create an alias for this auto location
FNROOT main ;this is the root of a call graph
FNSIZE main,4,0 ;main needs two 2-byte 'autos' (for result and incval)
FNCALL main,add ;main calls add
FNCALL main,incr ;main calls incr
PSECT code
main:
clrf result+0,c
clrf result+1,c
The main
routine requires four bytes of auto objects and so the
FNSIZE
directive has again been used to indicate this. The first
byte of main
's auto area can be accessed using the symbol
?au_main
; however, in this instance, an EQU
was
defined so that the more readible name result
could be used, as shown
in the clrf
instructions.
To be able to allocate memory on the stack, the linker needs to know how the program is structured in terms of calls. To allow it to form the program's call graph, several other directives are used.
The FNROOT
directive, shown above, indicates that the specified routine
forms the root node in a callgraph. (Alternatively, the resetVec
label
could have also been as the root node—it make no difference in this example.) The memory
allocated to stack objects can be overlapped with that of other routines within the same
callgraph, but no overlapping will take place between the stack objects of routines that
are in different callgraphs. Typically you will define one callgraph root for the main
part of your program and then one for each interrupt routine. This way, the stack memory
associated with interrupt routines is kept separate and no data corruption can
occur.
The FNCALL
directive is used as many times as required to indicate which
routines are called and from where. From this, the linker can form the callgraph nodes.
In the above code sequence, the FNCALL
directive was used twice. The
first indicates that the main
routine calls add
; the
second that main
calls incr
. As you develop your
program, you need to ensure that there is an FNCALL
directive for each
unique call that takes place in the code. If the called routine does not define any
compiled stack objects, the directive is not required, but it is good practice to
include it anyway, in case there are changes made to the program.
You may devise whatever convention you like to pass arguments to routines. In the
add
routine, the LSB of the first parameter has an offset of 0; the
MSB of the first parameter an offset of 1, etc., thus the expressions
?pa_add+0
and ?pa_add+1
represent the two bytes of
the first 'int' parameter and ?pa_add+2
and ?pa_add+3
represent the two bytes of the second parameter. The first auto-style object defined by
main
is referenced using the expressions
?au_main+0
(or result+0
) and
?au_main+1
(or result+1
).
Typically, routines that need to return a value do so by storing that value
into the memory taken up by their parameters. As the parameters should no longer be used
once the routine returns, this reuse is not normally an issue, but you do need to
consider the routine’s return value when you allocate memory for the routine's
parameters. The parameter memory you request for a routine using the
FNSIZE
directive must be the larger of the total size of the
routine's parameters and the size of the routine's return value.