compiler compilador c gcc

compilador - gcc wikipedia



gcc-O0 todavía optimiza el código "sin usar". ¿Hay una bandera de compilación para cambiar eso? (3)

Como -O0 en esta pregunta , gcc está eliminando (sí, con -O0 ) una línea de código _mm_div_ss(s1, s2); presumiblemente porque el resultado no se guarda. Sin embargo, esto debería desencadenar una excepción de punto flotante y generar SIGFPE, lo que no puede suceder si se elimina la llamada.

Pregunta : ¿Hay una bandera, o varias banderas, para pasar a gcc para que el código se compile tal como está? Estoy pensando en algo así como fno-remove-unused pero fno-remove-unused veo nada como eso. Idealmente, esto sería una bandera del compilador en lugar de tener que cambiar mi código fuente, pero si eso no es compatible ¿hay algún atributo / pragma de gcc para usar en su lugar?

Cosas que he intentado:

$ gcc --help=optimizers | grep -i remove

no hay resultados.

$ gcc --help=optimizers | grep -i unused

no hay resultados.

Y deshabilitando explícitamente todas las banderas de código muerto / eliminación - tenga en cuenta que no hay ninguna advertencia sobre el código no utilizado:

$ gcc -O0 -msse2 -Wall -Wextra -pedantic -Winline / -fno-dce -fno-dse -fno-tree-dce / -fno-tree-dse -fno-tree-fre -fno-compare-elim -fno-gcse / -fno-gcse-after-reload -fno-gcse-las -fno-rerun-cse-after-loop / -fno-tree-builtin-call-dce -fno-tree-cselim a.c a.c: In function ‘main’: a.c:25:5: warning: ISO C90 forbids mixed declarations and code [-Wpedantic] __m128 s1, s2; ^ $

Programa fuente

#include <stdio.h> #include <signal.h> #include <string.h> #include <xmmintrin.h> static void sigaction_sfpe(int signal, siginfo_t *si, void *arg) { printf("%d,%d,%d/n", signal, si!=NULL?1:0, arg!=NULL?1:0); printf("inside SIGFPE handler/nexit now./n"); exit(1); } int main() { struct sigaction sa; memset(&sa, 0, sizeof(sa)); sigemptyset(&sa.sa_mask); sa.sa_sigaction = sigaction_sfpe; sa.sa_flags = SA_SIGINFO; sigaction(SIGFPE, &sa, NULL); _mm_setcsr(0x00001D80); __m128 s1, s2; s1 = _mm_set_ps(1.0, 1.0, 1.0, 1.0); s2 = _mm_set_ps(0.0, 0.0, 0.0, 0.0); _mm_div_ss(s1, s2); printf("done (no error)./n"); return 0; }

Compilar el programa anterior da

$ ./a.out done (no error).

Cambiando la linea

_mm_div_ss(s1, s2);

a

s2 = _mm_div_ss(s1, s2); // add "s2 = "

produce el resultado esperado:

$ ./a.out inside SIGFPE handler

Edita con más detalles.

Esto parece estar relacionado con el atributo __always_inline__ en la definition _mm_div_ss .

$ cat t.c int div(int b) { return 1/b; } int main() { div(0); return 0; } $ gcc -O0 -Wall -Wextra -pedantic -Winline t.c -o t.out $

(sin advertencias o errores)

$ ./t.out Floating point exception $

vs below (mismo excepto por atributos de función)

$ cat t.c __inline int __attribute__((__always_inline__)) div(int b) { return 1/b; } int main() { div(0); return 0; } $ gcc -O0 -Wall -Wextra -pedantic -Winline t.c -o t.out $

(sin advertencias o errores)

$ ./t.out $

Agregar el atributo de función __warn_unused_result__ al menos da un mensaje útil:

$ gcc -O0 -Wall -Wextra -pedantic -Winline t.c -o t.out t.c: In function ‘main’: t.c:9:5: warning: ignoring return value of ‘div’, declared with attribute warn_unused_result [-Wunused-result] div(0); ^

editar:

Alguna discusión en la lista de correo de gcc . En definitiva, creo que todo está funcionando según lo previsto.


¿Por qué gcc no emite las instrucciones especificadas?

Un compilador produce código que debe tener el comportamiento observable especificado por el Estándar. Cualquier cosa que no sea observable puede cambiarse (y optimizarse) a voluntad, ya que no cambia el comportamiento del programa (como se especifica).

¿Cómo puedes superarlo?

El truco es hacer que el compilador crea que el comportamiento de la pieza de código en particular es realmente observable.

Dado que este es un problema que se encuentra con frecuencia en micro-benchmark, le aconsejo que busque cómo (por ejemplo) Google-Benchmark aborda esto. Desde benchmark_api.h obtenemos:

template <class Tp> inline void DoNotOptimize(Tp const& value) { asm volatile("" : : "g"(value) : "memory"); }

Los detalles de esta sintaxis son aburridos, para nuestro propósito solo necesitamos saber:

  • "g"(value) dice que el value se usa como entrada para el enunciado
  • "memory" es una barrera de lectura / escritura en tiempo de compilación

Entonces, podemos cambiar el código a:

asm volatile("" : : : "memory"); __m128 result = _mm_div_ss(s1, s2); asm volatile("" : : "g"(result) : );

Cual:

  • obliga al compilador a considerar que s1 y s2 pueden haberse modificado entre su inicialización y uso
  • obliga al compilador a considerar que se utiliza el resultado de la operación

No es necesario ningún indicador, y debería funcionar en cualquier nivel de optimización (lo probé en https://gcc.godbolt.org/ en -O3).


GCC no "optimiza" nada aquí. Simplemente no genera código inútil. Parece ser una ilusión muy común que hay una forma pura de código que el compilador debe generar y cualquier cambio en eso es una "optimización". No existe tal cosa.

El compilador crea una estructura de datos que representa lo que significa el código, luego aplica algunas transformaciones en esa estructura de datos y a partir de eso genera un ensamblador que luego se compila a las instrucciones. Si compila sin "optimizaciones" solo significa que el compilador solo hará el mínimo esfuerzo posible para generar código.

En este caso, el enunciado completo es inútil porque no hace nada y se descarta inmediatamente (después de expandir las líneas y lo que significan las instrucciones equivalentes a escribir a/b; la diferencia es que escribir a/b; emitirá una advertencia sobre la statement with no effect mientras que las instrucciones probablemente no se manejen con las mismas advertencias). Esto no es una optimización, el compilador tendría que gastar un esfuerzo extra para inventar el significado de un enunciado sin sentido, luego simular una variable temporal para almacenar el resultado de esta declaración y luego descartarla.

Lo que está buscando no son indicadores para deshabilitar optimizaciones, sino indicadores de pesimismo. No creo que ningún desarrollador de compiladores pierda tiempo implementando dichos indicadores. Aparte de tal vez como una broma de los inocentes de abril.


No soy un experto con las características internas de gcc , pero parece que tu problema no está en eliminar el código muerto mediante un pase de optimización. Lo más probable es que el compilador ni siquiera esté considerando generar este código en primer lugar.

Reduzcamos su ejemplo de intrínsecos específicos del compilador a una antigua adición simple:

int foo(int num) { num + 77; return num + 15; }

No hay código para + 77 generado :

foo(int): push rbp mov rbp, rsp mov DWORD PTR [rbp-4], edi mov eax, DWORD PTR [rbp-4] add eax, 15 pop rbp ret

Cuando uno de los operandos tiene efectos secundarios, solo ese operando es evaluado . Aún así, no hay adición en el conjunto.

Pero guardar este resultado en una variable (incluso no utilizada) obliga al compilador a generar código para sumar :

int foo(int num) { int baz = num + 77; return num + 15; }

Montaje:

foo(int): push rbp mov rbp, rsp mov DWORD PTR [rbp-20], edi mov eax, DWORD PTR [rbp-20] add eax, 77 mov DWORD PTR [rbp-4], eax mov eax, DWORD PTR [rbp-20] add eax, 15 pop rbp ret

La siguiente es solo una especulación, pero a partir de mi experiencia con la construcción de compiladores, es más natural no generar el código para las expresiones no utilizadas, en lugar de eliminar este código más adelante.

Mi recomendación es ser explícito sobre tus intenciones y poner el resultado de una expresión en una variable volatile (y, por lo tanto, no extraíble por el optimizador).

@Matthieu M señaló que no es suficiente para evitar la precomputación del valor. Entonces, para algo más que jugar con señales, debe usar formas documentadas para realizar la instrucción exacta que desee (probablemente, un ensamblaje en línea volatile ).