Looks like my concerns about using p registers were right.
I have tried to perform ldt16/stt16 with the p register in __asm__ block, but my program started to work abnormally. After further investigation, I found out that sdcc ignores fact that p virtual register is overriten by the custom asm code. For example, I have the following fucntion (this example with stt16, it is broken the same way ldt16 do):
#define __stt16(var) __asm__("mov a, "_STR_VAR(var)"\n"\
"mov p, a\n"\
"mov a, "_STR_VAR(var)"+1\n"\
"mov p+1, a\n"\
"stt16 p\n");
// ....
static uint16_t tm16Value = 0;
void reset_timer16_counter(void) {
uint8_t timerMode = T16M;
T16M = T16M_CLK_DISABLE;
__stt16(tm16Value);
T16M = timerMode;
}
The asm code generated for this function is the following:
0x004c: 0x0186 MOV A, IO(0x06) ;T16M
0x004d: 0x1700 MOV [0x00], A ; <---- sdcc uses p as local variable storage
0x004e: 0x5700 MOV A, 0x00
0x004f: 0x0106 MOV IO(0x06), A ;T16M
0x0050: 0x1f02 MOV A, [0x02]
0x0051: 0x1700 MOV [0x00], A ; <---- value overwritten by the __asm__ block
0x0052: 0x1f03 MOV A, [0x03]
0x0053: 0x1701 MOV [0x01], A
0x0054: 0x0600 STT16 [0x00]
0x0055: 0x1f00 MOV A, [0x00] ; <---- restored wrong value
0x0056: 0x0106 MOV IO(0x06), A ;T16M
0x0057: 0x007a RET
If I add additional context saving for __asm__ block, everything is working normally.
So for now, the most usable way of using ldt16/stt16 is to make sdcc to place uint16_t variables to the word aligned address by placing as the first items in the first translation unit as JS suggested, or by introducing the additional single byte variables before ldt16/stt16 operand variables if their address is non-aligned.