Por que o compilador GCC omite algum código?

9

Não consigo entender por que o compilador GCC cortou parte do meu código enquanto preserva absolutamente o mesmo na vizinhança?

O código C:

#define setb_SYNCO do{(PORTA|= (1<<0));} while(0);

ISR(INT0_vect){
    unsigned char i;

    i = 10;
    while(i>0)i--;   // first pause - omitted

    setb_SYNCO;
    setb_GATE;
    i=30;
    clrb_SYNCO;
    while(i>0)i--;  // second pause - preserved
    clrb_GATE;
}

A parte correspondente do LSS (arquivo do assembler, criado pelo compilador):

ISR(INT0_vect){
  a4:   1f 92           push    r1
  a6:   0f 92           push    r0
  a8:   0f b6           in  r0, 0x3f    ; 63
  aa:   0f 92           push    r0
  ac:   11 24           eor r1, r1
  ae:   8f 93           push    r24
    unsigned char i;

    i = 10;
    while(i>0)i--;

    setb_SYNCO;
  b0:   d8 9a           sbi 0x1b, 0 ; 27
    setb_GATE;
  b2:   d9 9a           sbi 0x1b, 1 ; 27
    i=30;
    clrb_SYNCO;
  b4:   d8 98           cbi 0x1b, 0 ; 27
  b6:   8e e1           ldi r24, 0x1E   ; 30
  b8:   81 50           subi    r24, 0x01   ; 1
    while(i>0)i--;
  ba:   f1 f7           brne    .-4         ; 0xb8 <__vector_1+0x14>
    clrb_GATE;
  bc:   d9 98           cbi 0x1b, 1 ; 27
}
  be:   8f 91           pop r24
  c0:   0f 90           pop r0
  c2:   0f be           out 0x3f, r0    ; 63
  c4:   0f 90           pop r0
  c6:   1f 90           pop r1
  c8:   18 95           reti

Eu poderia assumir que o compilador descobre que esse código é falso e o corta, mas por que ele preserva o mesmo no final do código?

Existem instruções do compilador para impedir essa otimização?

Roman Matveev
fonte
11
Você também pode dizer ao compilador para não otimizar uma única função, talvez valha a pena tentar esse método com seu ISR. Veja esta pergunta no stackoverflow.
Vladimir Cravero
2
Olá Roman, adicionei a tag "c" à sua pergunta removendo atmega. Eu tive que remover uma tag, já que há um limite (cinco), e ao fazer uma pergunta relacionada ao código, adicionar o nome do idioma como uma tag é ótimo porque todo o código (Perguntas e Respostas) fica destacado.
precisa saber é o seguinte
5
De um modo geral, idiomas de nível superior (como C) são explicitamente projetados para não serem vinculados a um relacionamento 1: 1 com o assembly resultante. Se você precisar contar as instruções para acertar o tempo, sempre precisará confiar na montagem (como algumas das respostas). O ponto principal das linguagens de alto nível é que o compilador tem alguma liberdade para ajudar a tornar seu código mais rápido, mais forte e melhor do que antes. Detalhes como alocações de registro e previsões de ramificação são muito melhores para o compilador ... exceto em momentos como este em que você, programador, sabe exatamente as instruções que deseja.
Cort Ammon
5
A melhor pergunta é: por que o GCC não está otimizando esses dois loops?
Ilmari Karonen
5
O Gcc primeiro desenrola o loop e só então percebe que o código correspondente é inútil. Com um tamanho de loop de 30, desenrolar seria tolo e o gcc não faz isso. Em um nível de otimização mais alto, ambos são otimizados.
Marc Glisse

Respostas:

9

Como em um comentário você declara que "cada marca de CPU vale a pena", sugiro o uso de um assembly em linha para fazer com que seus atrasos se repetam da maneira que desejar. Esta solução é superior às várias volatileou -O0porque deixa claro qual é a sua intenção.

unsigned char i = 10;
__asm__ volatile ( "loop: subi    %0, 0x01\n\t"
                   "      brne    loop"
                   : "+rm" (i)
                   : /* no inputs */
                   : /* no dirty registers to decleare*/);

Isso deve fazer o truque. A coisa volátil está aí para dizer ao compilador "Eu sei que isso não faz nada, apenas mantenha-o e confie em mim". As três "declarações" asm são bastante autoexplicativas, você pode usar qualquer registro em vez de r24, acredito que o compilador gosta de registros mais baixos, então você pode querer usar um registro alto. Após o primeiro, :você deve listar as variáveis ​​de saída (leitura e gravação) c, e não há nenhuma; após o segundo, :você deve listar as variáveis ​​de entrada (ronly) c; novamente, não há, e o terceiro parâmetro é uma lista separada por vírgula de registradores modificados , neste caso r24. Não tenho certeza se você deve incluir também o registro de status, pois o ZEROsinalizador muda de curso. Eu não o incluí.

editar resposta editada conforme solicitado pelo OP. Algumas notas.

O "+rm"antes (i)significa que você está deixando o compilador decidir colocar i em m Emory ou em uma r egister. Isso é bom na maioria dos casos, pois o compilador pode otimizar melhor se for gratuito. No seu caso, acredito que você deseja manter apenas a restrição r para forçar i a ser um registro.

Vladimir Cravero
fonte
Parece que isso é algo que realmente preciso. Mas você poderia modificar sua resposta para aceitar qualquer cvariável em vez de literal que 10eu mencionei na resposta original? Estou tentando ler os manuais do GCC sobre o uso adequado da construção de asm, mas está um pouco obscuro para mim agora. Eu ficaria muito grato!
Roman Matveev
11
@RomanMatveev editado conforme solicitado
Vladimir Cravero
13

Você pode tentar fazer o loop realmente fazer alguma coisa. No momento, o compilador está dizendo com razão "Este loop não está fazendo nada - eu me livrarei dele".

Então você pode tentar uma construção que eu uso com frequência:

int i;
for (i = 0; i < 10; i++) {
    asm volatile ("nop");
}

Nota: nem todos os destinos do compilador gcc usam a mesma sintaxe de montagem embutida - pode ser necessário ajustá-lo para o seu destino.

Majenko
fonte
Sua solução parece muito mais elegante que a minha ... talvez a minha seja melhor quando é necessária uma contagem PRECISA de ciclos? Quero dizer, não é garantido que todo o conjunto seja compilado de uma certa maneira, é?
precisa saber é o seguinte
8
O simples fato de usar C significa que você não pode garantir ciclos. Se você precisar de uma contagem precisa de ciclos, o ASM é o único caminho a percorrer. Quero dizer, você tem tempo diferente com o loop C se você tem -funroll-loops habilitado do que se não o fizer, etc.
Majenko
Sim, foi o que eu pensei. Ao fazer atrasos no HW com valores i altos o suficiente (100 ou mais), acho que sua solução produz virtualmente os mesmos resultados enquanto aprimora a legibilidade.
precisa saber é o seguinte
6

Sim, você poderia assumir isso. Se você declarar a variável i como volátil, instrua o compilador a não otimizar em i.

Ambiorix
fonte
11
Isso não é inteiramente verdade na minha opinião.
Vladimir Cravero
11
@VladimirCravero, o que você quer dizer com dizer isso? Você poderia deixar mais claro?
Roman Matveev
2
O que eu quis dizer é que não teria tanta certeza sobre o que um compilador faz. Declarar uma variável volátil informa ao compilador que ele pode mudar em outro lugar, portanto, isso realmente deve acontecer.
Vladimir Cravero
11
@ Roman Matveev, register unsigned char volatile i __asm__("r1");talvez?
a3f 26/01
2
Declarar icomo volátil resolve tudo. Isso é garantido pelo padrão C 5.1.2.3. Um compilador em conformidade não deve otimizar esses loops se ifor volátil. Felizmente, o GCC é um compilador em conformidade. Infelizmente, existem muitos possíveis compiladores C que não estão em conformidade com o padrão, mas isso é irrelevante para esta questão em particular. Não há absolutamente nenhuma necessidade de montador em linha.
Lundin
1

Após o primeiro loop ié uma constante. A inicialização ie o loop não produzem senão um valor constante. Nada no padrão especifica que esse loop deve ser compilado como está. O padrão também não diz nada sobre o tempo. O código compilado deve se comportar como se o loop estivesse presente e funciona. Você não pode dizer com segurança que essa otimização foi executada sob o padrão (o tempo não conta).

O segundo loop também deve ser excluído. Considero que é um bug (ou uma otimização ausente) que não é. Após o loop ié zero constante. O código deve ser substituído pela configuração icomo zero.

Acho que o GCC se mantém ipuramente pelo motivo de você executar um acesso à porta (opaco) que pode afetar i.

Usar

asm volatile ("nop");

induzir o GCC a acreditar que o loop faz alguma coisa.

usr
fonte