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.
- Declare the C prototype for the routine in C source code, choosing appropriate parameter and return value types.
- Include the
<xc.inc>
assembly header file. If this is included using#include
, ensure the source file uses .S as its extension. - If required, define each auto-like variable using the
stack_auto name,size
macro, wherename
can be any valid assembler identifier andsize
is the variable’s size in bytes. - If required, define each parameter using the macro
stack_param name,size
, wherename
can be any valid assembly identifier andsize
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. - Initialize the stack once using the macro
alloc_stack
before any instructions in the routine. - 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