5.6 Register Usage

The assembly generated from C source code by the compiler will use certain registers in the PIC MCU register set. Most importantly, the compiler assumes that nothing other than code it generates can alter the contents of these registers.

The registers that are special and that are used by the compiler are listed in the following table.

Table 5-10. Registers Used By The Compiler
Register nameApplicable devices
WREGAll 8-bit devices
STATUSAll 8-bit devices
PCLATHAll Mid-range devices
PCL, PCLATH, PCLATUAll PIC18 devices
BSREnhanced Mid-range and PIC18 devices
FSRNon-Enhanced Mid-range devices
FSR0L, FSR0H, FSR1L, FSR1HEnhanced Mid-range and PIC18 devices
FSR2L, FSR2HAll PIC18 devices
TBLPTRL, TBLPTRH, TBLPTRU, TABLATAll PIC18 devices
PRODL, PRODHAll PIC18 devices
Dynamic working registers (btemps)Enhanced Mid-range and PIC18 devices

The dynamic working registers (btemps) are objects that the compiler treats like registers. These objects are saved and restored like any other register when used in interrupt code. They might be used (along with the software stack) when a function needs to be reentrant, especially when the register-optimized reentrant stack is being used, as described in Register-optimized Reentrant Stack.

The compiler will not be aware of changes to a register’s value when the register itself is used in expressions as a C lvalue (assignment destination), so such statements should be used with caution. For example, if the statement WREG = 0; were encoded using the clrf instruction, the compiler would not consider this as modifying the W register. Additionally, registers should not be changed directly by in-line assembly code, as shown in the following example, which modifies the ZERO bit in the STATUS register using the macro defined by <xc.h>.
#include <xc.h>
void getInput(void)
{
    __asm("bcf " ZERO_bit); //do not write using inline assembly code
    process(c);
}

If any of the applicable registers listed in the table are used by interrupt code, they will be saved and restored when an interrupt occurs, either in hardware or software (see Context Switching).

Note that the compiler determines the size of all const data defined by a program. When building for PIC18 devices, the compiler can use this data to optimize program memory access. For total const data sizes less than 64k and when the mediumconst psect is used to hold this data, the compiler positions the mediumconst psect so that the TBLPTRU register only needs to be initialized at runtime startup and does not need to be modified during program execution. For total sizes less than 256 bytes, the psect holding this data (smallconst) is positioned so that both the upper and high table pointer registers (TBLPTRU and TBLPTRH) do not need to be changed and can be assumed to hold the value assigned at runtime startup. As a consequence, any code that modifies the table pointer registers without the compiler being "aware" of this operation could lead to code failure.
const array[] = { 0, 2, 4, 7};
// runtime startup sets TBLPTRU and TBLPTRH so that all smallconst psect is accessible 
int main(void) {
    ...
    data = array[idx]; // generated code only adjusts TBLPTRL to access array[]
    TBLPTR = 0x2000;   // code forces table pointer to change
    ...
    data = array[idx]; // code fails, assuming TBLPTRU and TBLPTRH still point to smallconst

Corruption of the table pointers could even occur if the program dereferences a pointer assigned an address of an object outside of the general purpose flash program memory. Check the assembly list file if you are unsure if your code modified the table pointer, and the following code can be used to verify if any code failure is attributable to this situation.

If your code does manually modify the table pointer, save the content of TBLPTRU and potentially TBLPTRH as required before the assignment, and restore the registers with their saved values afterward. The TBLPTRL register never needs to be saved, as it is always set by the compiler, but saving the entire table pointer is simpler than saving individual registers, for example.
#include <xc.h>
uint24_t saved_tblptr = TBLPTR;
// place code that manually modifies TBLPTR here
TBLPTR = saved_tblptr;