ret
в основном pop
в ПК.
Вы перешли на doFirst
вместо того, чтобы вызывать его, поэтому вы должны вернуться с rjmp
к метке внутри test
вместо ret
.
Или, если doFirst
была отдельной функцией, вы сделали оптимизированный хвостовой вызов, чтобы она вернулась к вашему вызывающему.(Например, как вы (или компилятор) реализовали бы int foo() { return bar(); }
)
Вам не нужно делать doFirst
отдельной функцией.Вы можете вставить его один раз, потому что он не нужен для возврата в 2 разных места в вашем цикле test
.
test:
cpi r16, 0
breq doFirst0;
cpi r16, 1
breq doFirst1;
donefirst:
cpi r16, 2
brne noSecond ; conditionally SKIP the inlined doSecond body
inc r17 ; inlined body of doSecond
noSecond:
rjmp test ; You don't need a slow 4-byte JMP, just a short relative jump
doFirst0:
inc r16
doFirst1: ; fall through instead of jumping back and finding that r16 == 1 now
inc r16
rjmp donefirst
Примечание: эта оптимизация изменит поведение для r16 == 0
, еслиу нас были обе команды breq
, прыгающие в одно и то же место .Мы только увеличиваем один раз, а затем выполняем оставшуюся часть цикла, вместо того, чтобы увеличивать, затем проверять и находить r16 == 1
и снова увеличивать.
На самом деле мы могли бы также сделать if(r16 <=0) r16 = 2;
с ldi r16, 2
Если он был слишком большим для дублирования, и вы действительно хотели rcall
/ ret
для вспомогательной функции, вы можете условно перепрыгнуть через rcall
инструкцию.
Использование 2 пар CPI / BREQ - очень неэффективный способ проверить r16 <= 1
(без знака). AVR имеет BRLO (BRanch, если LOwer (без знака)) .
Поскольку инструкции BR более быстрые, если не выполняются, мы оставим этот код вне строки вместо его вставки (например,Я сделал для doSecond) и перепрыгнул через него с BRSH (То же или выше).
test:
cpi r16, 1
brlo doFirst ;
donefirst: ; r16 will become 2, so we can't put this label later and skip the next check
cpi r16, 2
brne noSecond ; conditionally SKIP the inlined doSecond body
inc r17
noSecond: ; this label might as well be at test: directly
rjmp test
doFirst: ; rare case, only runs the first 2 iters, put it out of line
ldi r16, 2 ; 1 or 2 inc instructions make r16 = 2
rjmp donefirst
Весь этот цикл не имеет большого смысла, если обработчик прерываний не может изменить или использовать r16 или r17,хоть.Если это не так, вы действительно хотите просто очистить первую итерацию, а затем попасть в бесконечный цикл, который ничего не делает.
Когда мы входим в ваш цикл (если ваши ret
s пошли туда, где и должны)с r16 <= 2
мы получим r16 = 2
и r17
, увеличенные один раз на этой первой итерации.
test:
cpi r16, 2
brhi above2
ldi r16, 2 ; result of 1 or 2 inc instructions
; flags may differ from your version.
infloop_r16_eq_2: ; loop for the r16==2 case
inc r17
rjmp infloop_r16_eq_2
above2:
infloop: ; loop for the r16!=2 case
rjmp infloop
Но если регистры являются глобальными переменными, которые могут быть изменены асинхронноМы не можем просто проверить r16
один раз, а затем продолжить вечно.
Мне было любопытно, что будет делать gcc, поэтому я использовал register volatile unsigned char a asm("r16");
и "r17"
.Удивительно, но это частично работает, хотя и предупреждает, что оптимизация может исключить чтение и / или запись в регистр переменных.Похоже, это произошло для inc r17
с AVR gcc4.6.4 -O3, но не для -O1
.gcc7.3 для x86-64 фактически сохраняет его при -O3. Посмотрите в проводнике компилятора Godbolt и посмотрите также Как удалить "шум" из вывода сборки GCC / clang? .
// x86-64 and AVR both have r14 and r15, but not r16/r17
register volatile unsigned char a asm("r14");
register volatile unsigned char b asm("r15");
void test() {
while(1) {
if (a <= 1)
a++; // or a = 2;
if (a == 2)
b++;
}
}
AVR gcc -O1 вывод:
test:
.L7:
cpi r16,lo8(2)
brsh .L2
subi r16,lo8(-(1)) ; inc r16
.L2:
cpi r16,lo8(2)
brne .L7 ; back to the top
; else fall through
subi r17,lo8(-(1)) ; inc r17
rjmp .L7 ; then back to the top
Это выглядит довольно разумно.
lo8(2)
только 2, я не знаю, почему gcc генерирует сборку таким образом.Возможно, для адресов символов или чего-то подобного, например lo8(test)
, полезно получить младший байт адреса метки.
Это своего рода оптимизация дублирования хвоста цикла, но один из хвостов пуст.Таким образом, вместо того, чтобы пропустить inc r17
, мы просто прыгаем прямо к вершине цикла.
brne .L7
может перейти к инструкции brsh
, поскольку флаги по-прежнему установлены в cpi r16, 2
.Gcc не делает этого, потому что мы сказали, что регистры изменчивы, поэтому он не оптимизирует второе чтение регистра.