5.5 Using a Compiled Stack
Not to be confused with a software stack, which is a dynamic stack allocation accessed via a stack pointer, a compiled stack is a memory area that is designated for the static allocation of local objects that should only consume memory for the duration of the routine with which they are associated. This is similar to the stack-based auto and parameter objects defined by functions in C programs. When using the PIC Assembler, the allocation of memory on the stack is performed by the linker. The stack-based objects requested by each routine are assembled into blocks. Blocks from routines that are not active at the same time can be overlaid in memory, based on information in the program's callgraph, which is constructed from directives added to your program.
A more detailed example of using a compiled stack with the PIC Assembler is provided in the MPLAB℠ XC8 PIC Assembler User's Guide for Embedded Engineers. A summary of this information is provided here.
The FNCONF directive (see Fnconf Directive) should be used
            once per program. It's three arguments indicate to the linker 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 parameter objects.
The FNROOT directive (see Fnroot Directive) should be used
            once for each routine that forms the root node in a callgraph. 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 FNSIZE directive (see Fnsize Directive) should be
            specified for each routine that needs to have objects plaed on the compiled stack. It
            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. The directive can be placed anywhere in your code, but it is
            often located near the routine it configures.
The FNCALL directive (see Fncall Directive) is used as many
            times as required to indicate which routines (that use the compiled stack) are called
            and from where. From this information, the linker can form the callgraph. As you develop
            your program, you will need to ensure that there is an FNCALL directive
            for each unique call that takes place in your code. If a called routine does not define
            any compiled stack objects, the directive is not required for that routine, but it is
            good practice to include it anyway, in case there are subsequent changes made to the
            program.
FNSIZE directive. The following example
            of a cut-down program shows the stack being set up, with the main and add routines
            requesting that they each require space on the stack. The main routines reads the
            special symbols created by the linker for its auto-like objects; the add routine uses
            special symbols for its
            parameters..FNCONF udata_acs,?au_,?pa_        ;setup the stack
FNROOT main                       ;the main routine is a callgraph root
PSECT code
FNSIZE main,4,0                   ;the main routine needs 4 bytes of auto-like objects
GLOBAL ?au_main                   ;make the symbol created by the linker globally accessible
main:
    ...
loop:
    movff   ?au_main+0,?pa_add+0  ;load the first byte of the first parameter
    movff   ?au_main+1,?pa_add+1  ;load the second byte of the first parameter
    movff   ?au_main+2,?pa_add+2  ;load the first byte of the second parameter
    movff   ?au_main+3,?pa_add+3  ;load the second byte of the second parameter
FNCALL main,add                   ;the main routine will call the add routine in the callgraph
    call    add                   ;the actual call
    ...
FNSIZE add,0,4                    ;the add routine needs 4 bytes of parameters
GLOBAL ?pa_add                    ;make the symbol created by the linker globally accessible
add:
    movf    ?pa_add+2,w,c         ;the add routine uses its parameter symbols
    addwf   ?pa_add+0,f,c
    ...