¿Por qué el compilador GCC omite algún código?

9

No puedo entender por qué el compilador de GCC corta parte de mi código mientras conserva absolutamente el mismo en el vecindario.

El 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;
}

La parte correspondiente de LSS (archivo ensamblador, creado por el 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

Podría suponer que el compilador descubre que dicho código es ficticio y lo corta, pero ¿por qué conserva el mismo al final del código?

¿Hay alguna instrucción del compilador para evitar dicha optimización?

Roman Matveev
fuente
1
También puede decirle al compilador que no optimice una sola función, tal vez valga la pena probar este método con su ISR. Ver esta pregunta en stackoverflow.
Vladimir Cravero
2
Hola Roman, agregué la etiqueta "c" a tu pregunta para eliminar atmega. Tuve que eliminar una etiqueta ya que hay un límite (cinco), y al hacer una pregunta relacionada con el código, agregar el nombre del idioma como etiqueta es excelente porque se resalta todo el código (preguntas y respuestas).
Vladimir Cravero
55
En términos generales, los lenguajes de nivel superior (como C) están diseñados explícitamente para no estar vinculados a una relación 1: 1 con su ensamblaje resultante. Si necesita contar las instrucciones para acertar, siempre debe confiar en el ensamblaje (como lo hicieron algunas de las respuestas). El objetivo de los lenguajes de alto nivel es que el compilador tiene cierta libertad para ayudar a que su código sea más rápido, más fuerte y mejor que antes. Detalles como las asignaciones de registros y las predicciones de bifurcación son mucho mejores para el compilador ... excepto en momentos como este donde usted, el programador, sabe exactamente las instrucciones que desea.
Cort Ammon
55
La mejor pregunta es, ¿por qué GCC no está optimizando ambos bucles?
Ilmari Karonen
55
Gcc primero desenrolla el bucle y solo luego se da cuenta de que el código correspondiente es inútil. Con un tamaño de bucle de 30, el desenrollado sería una tontería y gcc no lo hace. En un nivel de optimización más alto, ambos están optimizados.
Marc Glisse

Respuestas:

9

Dado que en un comentario declaras que "cada tic de CPU es digno", sugiero usar un ensamblaje en línea para hacer que tus retrasos se repitan como quieras. Esta solución es superior a las diversas volatileo -O0porque deja en claro cuál es su intención.

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

Eso debería hacer el truco. Lo volátil está ahí para decirle al compilador "Sé que esto no hace nada, solo guárdalo y confía en mí". Las tres "declaraciones" asm se explican por sí mismas, puede usar cualquier registro en lugar de r24, creo que al compilador le gustan los registros inferiores, por lo que es posible que desee usar uno alto. Después de la primera :, debe enumerar las variables de salida (lectura y escritura) c, y no hay ninguna, después de la segunda :, debe enumerar las variables de entrada (ronly) c, nuevamente, no hay ninguna, y el tercer parámetro es una lista separada por comas de registros modificados , en este caso r24. No estoy seguro de si debe incluir también el registro de estado, ya que la ZERObandera cambia, por supuesto, no lo incluí.

edite la respuesta editada según lo solicitado por OP. Algunas notas.

El "+rm"antes (i)significa que usted está dejando que el compilador decide colocar i en m Emory o en un r egistro. Eso es algo bueno en la mayoría de los casos ya que el compilador puede optimizar mejor si es gratis. En su caso, creo que desea mantener solo la restricción r para forzar a i a ser un registro.

Vladimir Cravero
fuente
Parece que esto es algo que realmente necesito. ¿Pero podría modificar su respuesta para aceptar cualquier cvariable en lugar del literal 10que mencioné en la respuesta original? Estoy tratando de leer los manuales de GCC sobre el uso adecuado de la construcción de asm, pero ahora estoy un poco oscurecido. ¡Te lo agradecería mucho!
Roman Matveev
1
@RomanMatveev editado como lo solicitó
Vladimir Cravero
13

Podrías intentar hacer que el ciclo realmente haga algo. Tal como está, el compilador dice con razón: "Este ciclo no está haciendo nada, me deshaceré de él".

Entonces podrías probar una construcción que uso con frecuencia:

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

Nota: no todos los objetivos para el compilador gcc usan la misma sintaxis de ensamblaje en línea; es posible que deba ajustarlo para su objetivo.

Majenko
fuente
Su solución parece mucho más elegante que la mía ... ¿quizás la mía es mejor cuando se requiere un recuento de ciclo PRECISE? Quiero decir, no se garantiza que todo se compile de cierta manera, ¿verdad?
Vladimir Cravero
8
El simple hecho de que está usando C significa que no puede garantizar ciclos. Si necesita un recuento de ciclos preciso, ASM es el único camino a seguir. Quiero decir, obtienes una sincronización diferente con el bucle C si tienes -funroll-loops habilitados que si no lo tienes, etc.
Majenko
Sí, eso es lo que yo pensaba. Al hacer retrasos HW con valores de i suficientemente altos (100 o más), supongo que su solución produce prácticamente los mismos resultados al tiempo que mejora la legibilidad.
Vladimir Cravero
6

Sí, puedes asumir eso. Si declaras que la variable i es volátil, le dices al compilador que no optimice en i.

Ambiorix
fuente
1
Eso no es del todo cierto en mi opinión.
Vladimir Cravero
1
@VladimirCravero, ¿qué quieres decir con eso? ¿Podrías dejar más claro?
Roman Matveev el
2
Lo que quise decir es que no estaría tan seguro de lo que hace un compilador. Declarar una variable volátil le dice al compilador que podría cambiar en otro lugar, por lo que realmente debería hacerlo mientras.
Vladimir Cravero
1
@Roman Matveev register unsigned char volatile i __asm__("r1");tal vez?
a3f
2
Declarar icomo volátil resuelve todo. Esto está garantizado por el estándar C 5.1.2.3. Un compilador conforme no debe optimizar esos bucles si ies volátil. Afortunadamente, GCC es un compilador conforme. Desafortunadamente, hay muchos posibles compiladores de C que no se ajustan al estándar, pero eso es irrelevante para esta pregunta en particular. No hay absolutamente ninguna necesidad de ensamblador en línea.
Lundin
1

Después del primer bucle ies una constante. La inicialización de iy el bucle no hacen más que producir un valor constante. Nada en el estándar especifica que este bucle debe compilarse tal cual. El estándar tampoco dice nada sobre el tiempo. El código compilado debe comportarse como si el bucle estuviera presente y lo hace. No se puede decir de manera confiable que esta optimización se realizó bajo el estándar (el tiempo no cuenta).

El segundo bucle también debe eliminarse. Considero que es un error (o una optimización que falta) que no lo es. Después del ciclo ies constante cero. El código debe reemplazarse con la configuración ia cero.

Creo que GCC se mantiene iúnicamente por la razón por la que puede afectar el acceso a un puerto (opaco) i.

Utilizar

asm volatile ("nop");

engañar a GCC para que crea que el ciclo hace algo.

usr
fuente