Когда был реализован сервер AVR G CC и был разработан avr-g cc ABI , оказалось, что генерация кода может быть улучшена в некоторых ситуациях, когда есть регистр, который как известно, содержит 0
. Тогда автор выбрал R1
, т. Е. Когда avr-g cc печатает инструкции по сборке, можно предположить, что R1=0
как в этом примере:
unsigned add (unsigned x, unsigned char y)
{
if (x != 64)
return x + y;
else
return x;
}
Это компилируется с -c -Os -save-temps
в код ниже. Он использует R1
ака. __zero_reg__
, чтобы можно было распечатать более короткую последовательность команд:
__zero_reg__ = 1
add:
cpi r24,64
cpc r25,__zero_reg__
breq .L2
add r24,r22
adc r25,__zero_reg__
.L2:
ret
R1
было выбрано, потому что в AVR старшие регистры более мощные и, следовательно, распределение регистров начинается - с частичкой соли - с старшие регистры, следовательно, младшие регистры будут использоваться последними. Таким образом, был использован регистр с небольшим номером регистра.
Этот специальный регистр не управляется распределителем регистров, он «фиксирован» и управляется вручную. Это было все просто с ранними AVR, которые не поддерживали инструкции MUL
. Однако с введением MUL
и двоюродных братьев все стало сложнее, поскольку MUL
использует регистровую пару R1:R0
в качестве неявного выходного регистра и, следовательно, переопределяет 0
, хранящийся в __zero_reg__
.
Таким образом Вы можете реализовать два подхода:
- Излучать
CLR __zero_reg__
до для каждого использования, поэтому R1
содержит 0
. - Очистите этот регистр 'после' последовательность, которая засоряла его.
В бэкэнде avr реализован подход 2.
Поскольку в текущем бэкэнде avr (по крайней мере до v10) этот регистр управляется вручную, существует нет информации о том, действительно ли необходимо очистить этот регистр или может быть пропущено:
unsigned char mul (unsigned char x)
{
return x * x * x;
}
производит с -c -Os -mmcu=atmega8 -save-temps
:
mul:
mul r24,r24
mov r25,r0
clr r1
mul r25,r24
mov r24,r0
clr r1
ret
т.е. R1
очищается дважды хотя сразу после 1-го «CLR» инструкция «MUL» снова переопределяет его. В принципе, серверная часть avr может отследить, какие команды clobber R1
и какие инструкции (последовательности) требуют R1=0
, однако это в настоящее время (v10) не реализовано.
Введение MUL
привело к еще одно осложнение: R1
не больше всегда ноль, т. е. когда прерывание срабатывает сразу после MUL
, тогда регистр в общем случае не ноль. Таким образом, подпрограмма обработки прерываний (ISR) должна сохранять + восстанавливать ее, когда она может использовать R1
:
#include <avr/interrupt.h>
char volatile v;
ISR (__vector_1)
{
v = 0;
}
Компиляция, сборка и затем avr-objdump -d
в объектном файле читает:
00000000 <__vector_1>:
0: 1f 92 push r1
2: 1f b6 in r1, 0x3f
4: 1f 92 push r1
6: 11 24 eor r1, r1
8: 10 92 00 00 sts 0x0000, r1
c: 1f 90 pop r1
e: 1f be out 0x3f, r1
10: 1f 90 pop r1
12: 18 95 reti
Полезная нагрузка ISR - просто sts ..., r1
, в которой хранится от 0
до v
. Это требует R1=0
, следовательно, необходимо clr r1
, следовательно, save-restore R1
с помощью push + pop. clr
закрывает состояние программы (SREG по адресу ввода-вывода 0x3f), поэтому SREG также должен быть сохранен-восстановлен вокруг этой последовательности, и для достижения sh, что компилятор использует r1
в качестве рабочего регистра поскольку регистры специальных функций нельзя использовать с push
/ pop
.
Кроме того, существуют ситуации, когда no сбрасывает нулевой регистр после MUL
:
int square (int a)
{
return a * a;
}
компилируется в:
mul r24,r24
movw r18,r0
mul r24,r25
add r19,r0
add r19,r0
clr r1
movw r24,r18
ret
Причина отсутствия CLR
после 1-го MUL
заключается в том, что последовательность умножения внутренне представлена и затем выдается как один кусок (insn) , следовательно, есть знания, что нет необходимости в промежуточном CLR
. Однако в приведенном выше примере с x * x * x
внутренним представлением является два insns, по одному для любого умножения.