5.12.3.2 Writing Reentrant Assembly Routines With Parameters

Hand-written assembly routines for Enhanced Mid-range and PIC18 devices can be written to use the software stack and be reentrantly called from C code. Such routines can take parameters, return values, and define their own local objects, if required.

The following are the steps that need to be followed to create such routines.

  1. Declare the C prototype for the routine in C source code, choosing appropriate parameter and return value types.
  2. Include the <xc.inc> assembly header file. If this is included using #include, ensure the source file uses .S as its extension.
  3. If required, define each auto-like variable using the stack_auto name,size macro, where name can be any valid assembler identifier and size is the variable’s size in bytes.
  4. If required, define each parameter using the macro stack_param name,size, where name can be any valid assembly identifier and size is the variable’s size in bytes. Parameters must be defined after autos and their order must match the order in which they appear in the C prototype.
  5. Initialize the stack once using the macro alloc_stack before any instructions in the routine.
  6. Immediately before each return instruction, restore the stack using the macro restore_stack

Write the routine in assembly in the usual way, taking note of the points in 5.12.1 Integrating Assembly Language Modules.

Each auto and parameter variable will be located at a unique offset to the stack pointer (FSR1). If you follow the above guidelines, you can use the symbol name_offset, which will be assigned the stack-pointer offset for the variable with name. These macros will exist for both auto and parameter variables.

If the routine returns a value, this must be placed into the location expected by the code that calls the routine (for full details of C-callable routines, see 5.8.6.2 Software Stack Return Values). To summarize, for objects 1 to 4 bytes in size, these must be loaded to temporary variables referenced as btemp, plus an offset. This symbol is automatically linked into your routine if you use the macros described above.

It is recommended that you do not arbitrarily adjust the stack pointer during the routine. The symbols that define the offset for each auto and parameter variable assume that the stack pointer has not been modified. However, if your assembly routine calls other reentrant routines (regardless of whether they are defined in C or assembly code), you must write the assembly code that pushes the arguments onto the stack, calls the function and then removes any return value from the stack.

The following is an example of a reentrant assembly routine, _inc, written for a PIC16F1xxx device. Its arguments and return value are described by the C prototype:

extern reentrant int inc(int foo);

This routine returns an int value that is one higher than the int argument that is passed to it. It uses an auto variable, x, strictly for illustrative purposes.

#include <xc.inc>
PSECT text2,local,class=CODE,delta=2
GLOBAL _inc
_inc:
    stack_auto x,2      ;an auto called 'x'; 2 bytes wide
    stack_param foo,2   ;a parameter called 'foo'; 2 bytes wide
    alloc_stack
    ;x = foo + 1;
    moviw   [foo_offset+0]FSR1
    addlw   low(01h)
    movwf   btemp+0
    moviw   [foo_offset+1]FSR1
    movwf   btemp+1
    movlw   high(01h)
    addwfc  btemp+1,f
    movf    btemp+0,w
    movwi   [x_offset+0]FSR1
    movf    btemp+1,w
    movwi   [x_offset+1]FSR1
    ;return x;
    moviw   [x_offset+0]FSR1
    movwf   btemp+0
    moviw   [x_offset+1]FSR1
    movwf   btemp+1
    restore_stack
    return

The following is an example of a reentrant assembly routine, _add, written for a PIC18 device. Its arguments and return value are described by the C prototype:

extern reentrant int add(int base, int index);

This routine returns an int value that is one higher than the int sum of the base and index arguments that are passed to it. It uses the auto variables, tmp and result, strictly for illustrative purposes.

#include <xc.inc>
psect		text1,class=CODE,space=0,reloc=2
GLOBAL _add
_add:
    stack_auto tmp,2      ;an auto called 'tmp'; 2 bytes wide
    stack_auto result,2   ;an auto called 'result'; 2 bytes wide
    stack_param base,2    ;a parameter called 'base'; 2 bytes wide
    stack_param index,2   ;a parameter called 'index'; 2 bytes wide
    alloc_stack
    ;tmp = base + index;
    movlw   base_offset
    movff   PLUSW1,btemp+0
    movlw   base_offset+1
    movff   PLUSW1,btemp+1
    movlw   index_offset
    movf    PLUSW1,w,c
    addwf   btemp+0,f,c
    movlw   index_offset+1
    movf    PLUSW1,w,c
    addwfc  btemp+1,f,c
    movlw   tmp_offset
    movff   btemp+0,PLUSW1
    movlw   tmp_offset+1
    movff   btemp+1,PLUSW1
    ;result = tmp + 1;
    movlw   tmp_offset
    movf    PLUSW1,w,c
    addlw   1
    movwf   btemp+0,c
    movlw   tmp_offset+1
    movff   PLUSW1,btemp+1
    movlw   0
    addwfc  btemp+1,f,c
    movlw   result_offset
    movff   btemp+0,PLUSW1
    movlw   result_offset+1
    movff   btemp+1,PLUSW1
    ;return result;
    movlw   result_offset
    movff   PLUSW1,btemp+0
    movlw   result_offset+1
    movff   PLUSW1,btemp+1
    restore_stack
    return