сгенерированная сборка avr-gcc при настройке реестра

Я смотрю на asm, сгенерированный из следующего кода C.

uint8_t anode = lednum / 4;
PORTB = (1 << anode);

Используя оптимизацию O2, я получаю следующее:

00000040 <setout>:
  40:   86 95           lsr r24
  42:   86 95           lsr r24
  44:   21 e0           ldi r18, 0x01   ; 1
  46:   30 e0           ldi r19, 0x00   ; 0
  48:   08 2e           mov r0, r24
  4a:   01 c0           rjmp    .+2     ; 0x4e
  4c:   22 0f           add r18, r18
  4e:   0a 94           dec r0
  50:   ea f7           brpl    .-6     ; 0x4c
  52:   28 bb           out 0x18, r18   ; 24
  54:   08 95           ret

Я понимаю, что lsr просто сдвигается на два (делится на четыре), но почему вся следующая работа выполняется в двух регистрах? Это потому, что 1 интерпретируется как int, 16 бит или что-то еще. Я могу представить, что нужен только один регистр, например r18.

[edit1] Добавлен полный код

[edit2] Согласно http://gcc.gnu.org/wiki/avr-gcc#Register_Layout r18 и r19 можно использовать внутри любой функции (кроме ISR) без восстановления. Ни то, ни другое не используется вне функции.

[edit3] Полный минимальный пример.

#include <avr/io.h>
#include <stdint.h>

void
setout(uint8_t lednum)
{
        uint8_t anode = lednum / 4;
        PORTB = (1 << anode);
}

void
main(void)
{
        while(1)
        {
            for (int i = 0; i < 10; ++i)
                setout(i);
        }
}

Дает мне:

bin/scanner.elf:     file format elf32-avr


Disassembly of section .text:

00000000 <__vectors>:
   0:   0e c0           rjmp    .+28        ; 0x1e <__ctors_end>
   2:   15 c0           rjmp    .+42        ; 0x2e <__bad_interrupt>
   4:   14 c0           rjmp    .+40        ; 0x2e <__bad_interrupt>
   6:   13 c0           rjmp    .+38        ; 0x2e <__bad_interrupt>
   8:   12 c0           rjmp    .+36        ; 0x2e <__bad_interrupt>
   a:   11 c0           rjmp    .+34        ; 0x2e <__bad_interrupt>
   c:   10 c0           rjmp    .+32        ; 0x2e <__bad_interrupt>
   e:   0f c0           rjmp    .+30        ; 0x2e <__bad_interrupt>
  10:   0e c0           rjmp    .+28        ; 0x2e <__bad_interrupt>
  12:   0d c0           rjmp    .+26        ; 0x2e <__bad_interrupt>
  14:   0c c0           rjmp    .+24        ; 0x2e <__bad_interrupt>
  16:   0b c0           rjmp    .+22        ; 0x2e <__bad_interrupt>
  18:   0a c0           rjmp    .+20        ; 0x2e <__bad_interrupt>
  1a:   09 c0           rjmp    .+18        ; 0x2e <__bad_interrupt>
  1c:   08 c0           rjmp    .+16        ; 0x2e <__bad_interrupt>

0000001e <__ctors_end>:
  1e:   11 24           eor r1, r1
  20:   1f be           out 0x3f, r1    ; 63
  22:   cf e5           ldi r28, 0x5F   ; 95
  24:   d1 e0           ldi r29, 0x01   ; 1
  26:   de bf           out 0x3e, r29   ; 62
  28:   cd bf           out 0x3d, r28   ; 61
  2a:   0d d0           rcall   .+26        ; 0x46 <main>
  2c:   1e c0           rjmp    .+60        ; 0x6a <_exit>

0000002e <__bad_interrupt>:
  2e:   e8 cf           rjmp    .-48        ; 0x0 <__vectors>

00000030 <setout>:
  30:   86 95           lsr r24
  32:   86 95           lsr r24
  34:   21 e0           ldi r18, 0x01   ; 1
  36:   30 e0           ldi r19, 0x00   ; 0
  38:   08 2e           mov r0, r24
  3a:   01 c0           rjmp    .+2         ; 0x3e <__SP_H__>
  3c:   22 0f           add r18, r18
  3e:   0a 94           dec r0
  40:   ea f7           brpl    .-6         ; 0x3c <setout+0xc>
  42:   28 bb           out 0x18, r18   ; 24
  44:   08 95           ret

00000046 <main>:
  46:   40 e0           ldi r20, 0x00   ; 0
  48:   21 e0           ldi r18, 0x01   ; 1
  4a:   30 e0           ldi r19, 0x00   ; 0
  4c:   84 2f           mov r24, r20
  4e:   86 95           lsr r24
  50:   86 95           lsr r24
  52:   b9 01           movw    r22, r18
  54:   02 c0           rjmp    .+4         ; 0x5a <main+0x14>
  56:   66 0f           add r22, r22
  58:   77 1f           adc r23, r23
  5a:   8a 95           dec r24
  5c:   e2 f7           brpl    .-8         ; 0x56 <main+0x10>
  5e:   68 bb           out 0x18, r22   ; 24
  60:   4f 5f           subi    r20, 0xFF   ; 255
  62:   4a 30           cpi r20, 0x0A   ; 10
  64:   98 f3           brcs    .-26        ; 0x4c <main+0x6>
  66:   40 e0           ldi r20, 0x00   ; 0
  68:   f1 cf           rjmp    .-30        ; 0x4c <main+0x6>

0000006a <_exit>:
  6a:   f8 94           cli

0000006c <__stop_program>:
  6c:   ff cf           rjmp    .-2         ; 0x6c <__stop_program>

Похоже, что он встроен, но он по-прежнему использует два регистра вместо одного.


person evading    schedule 09.05.2014    source источник
comment
Похоже, что он выполняет сдвиг анода на 1 ‹<, используя цикл, удваивающий значение; возможно, в наборе инструкций avr отсутствует инструкция смещения по счетчику? Кажется, что r19 не используется в коде, который вы публикуете, поэтому, не видя больше, трудно сказать, почему он задействован. И мы не знаем, используется ли снова исходное значение r24, оправдывая его копирование в другой регистр для сдвига. Или, возможно, у процессора есть ограничения на использование некоторых регистров.   -  person Chris Stratton    schedule 09.05.2014
comment
Да, это я знаю. Спрашивается, почему это сделано в r18 и r19, а не только в r18.   -  person evading    schedule 09.05.2014
comment
@ это оно. после этого есть только ret инструкция   -  person evading    schedule 09.05.2014
comment
Это немного озадачивает. У r19 есть какая-то особая роль в другом месте кода? Если заменить его каким-нибудь NOP, программа все равно будет работать?   -  person Chris Stratton    schedule 09.05.2014
comment
@ChrisStratton На самом деле он вообще не используется. Согласно gcc.gnu.org/wiki/avr-gcc#Register_Layout, можно изменить внутри любой функции. Он не используется вне функции, если я помещаю вызов setout как единственное в основном цикле.   -  person evading    schedule 09.05.2014
comment
позвольте нам продолжить обсуждение в чате   -  person evading    schedule 09.05.2014
comment
Я попытался воспроизвести это (с помощью своей собственной аналогичной программы) и обнаружил, что на самом деле я вижу полные 16-битные операции с avr-gcc 4.3.2 (добавление для r18, а затем adc для r19), сгенерированные для 8-битных типы. avr-gcc немного странно, потому что это 8-битное ядро, но 16-битное int, и правила, кажется, иногда вызывают продвижение. См. gcc.gnu.org/bugzilla/show_bug.cgi?id=34791 Возможно, ваше единственное появление r19 - это остаток попытки удалить ненужные 16-битные рекламные объявления.   -  person Chris Stratton    schedule 09.05.2014
comment
Скорее всего 1) Оператор << увеличивает байт до полного int (16 бит в R18 / R19). 2) Компилятор понимает, что требуется только 8-битный результат, поскольку PORTB всего 8-битный, поэтому он выполняет сдвиг только на младшем байте, который ему понадобится. 3) Оптимизатору не удается удалить R19. Похоже на ограничение оптимизатора.   -  person Joachim Isaksson    schedule 09.05.2014


Ответы (1)


gcc 4.9.0 немного хуже, сжигает два регистра плюс добавляет инструкцию по сравнению с тем, что вы используете.

#define PORTB (*(volatile unsigned char *)(0x18+0x20))
void setout(unsigned char lednum)
{
        unsigned char  anode = lednum / 4;
        PORTB = (1 << anode);
}

avr-gcc -O2 -mmcu=avr2 -c fun.c -o fun.o
avr-objdump -D fun.o

00000000 <setout>:
   0:   28 2f           mov r18, r24
   2:   26 95           lsr r18
   4:   26 95           lsr r18
   6:   81 e0           ldi r24, 0x01   ; 1
   8:   90 e0           ldi r25, 0x00   ; 0
   a:   02 2e           mov r0, r18
   c:   00 c0           rjmp    .+0         ; 0xe <setout+0xe>
   e:   88 0f           add r24, r24
  10:   0a 94           dec r0
  12:   02 f4           brpl    .+0         ; 0x14 <setout+0x14>
  14:   88 bb           out 0x18, r24   ; 24
  16:   08 95           ret

Я согласен с Иоахимом, я думаю, что 1 продвигается к чему-то большему. Это похоже на ошибку, которую делают люди, когда:

float a;
...
a = a + 1.0;

Если ты сделаешь это

#define PORTB (*(volatile unsigned char *)(0x18+0x20))
void setout(unsigned char one, unsigned char lednum)
{
        unsigned char  anode = lednum / 4;
        PORTB = (one << anode);
}

Я понял это

00000000 <setout>:
   0:   66 95           lsr r22
   2:   66 95           lsr r22
   4:   06 2e           mov r0, r22
   6:   00 c0           rjmp    .+0         ; 0x8 <setout+0x8>
   8:   88 0f           add r24, r24
   a:   0a 94           dec r0
   c:   02 f4           brpl    .+0         ; 0xe <setout+0xe>
   e:   88 bb           out 0x18, r24   ; 24
  10:   08 95           ret

Я думаю, что компилятор заставляет эту константу иметь 16-битное значение в соответствии с правилами, но затем оптимизатор выясняет, что ему не нужно сдвигать 16 бит. Назначение 16-битной константы остается. Итак, я думаю, что Иоахим справился с этим ... Опубликуйте ответ, чтобы мы могли проголосовать за него ...

person old_timer    schedule 09.05.2014
comment
Я знаю, что могу сделать 1L продвигаться долго. Есть что-то похожее на char? - person evading; 09.05.2014
comment
@evading - Я пробовал версию, в которой я приводил 1 к uint8_t, и, похоже, это не имело значения; продвижение похоже на правила языка C. - person Chris Stratton; 09.05.2014
comment
@ChrisStratton Я пробовал то же (uint8_t)(1), что и ты, и получил тот же результат. Так что мне было интересно, есть ли способ сделать 1UC, но я никогда не видел, чтобы это было сделано. - person evading; 09.05.2014