2012-08-22 34 views
17

tengo este pequeño fragmento de código (este es un ejemplo de trabajo mínima del problema que tengo):comportamiento extraño con optimizaciones habilitadas

#include <stdio.h> 
#include <stdlib.h> 
#include <string.h> 

void xorBuffer(unsigned char* dst, unsigned char* src, int len) 
{ 
    while (len != 0) 
    { 
     *dst ^= *src; 
     dst++; 
     src++; 
     len--; 
    } 
} 

int main() 
{ 
    unsigned char* a = malloc(32); 
    unsigned char* b = malloc(32); 
    int t; 

    memset(a, 0xAA, 32); 
    memset(b, 0xBB, 32); 

    xorBuffer(a, b, 32); 

    printf("result = "); 
    for (t = 0; t < 32; t++) printf("%.2x", a[t]); 
    printf("\n"); 

    return 0; 
} 

Este código se supone que debe realizar la o-exclusiva de dos 32- Buffers de memoria de bytes (conceptualmente, esto debería hacer a = a^b). Como 0xAA^0xBB = 0x11, debería imprimir "11" treinta y dos veces.

Mi problema es que cuando compilo esto en MinGW-GCC (Windows), esto funciona perfectamente en modo de depuración (sin optimizaciones) pero se bloquea con un SIGILL a mitad del ciclo xorBuffer cuando las optimizaciones a partir de -O3 están habilitadas. Además, si pongo un printf en el bucle ofensivo, funcionará perfectamente de nuevo. Sospecho que hay corrupción en la pila, pero no veo lo que estoy haciendo mal aquí.

intentando depurar con GDB con optimizaciones habilitadas es una causa perdida como muestra toda BGF me está "optimizado variable de salida" para todas las variables (y, por supuesto, si lo intento y printf a cabo una variable, que va a trabajar pronto)

¿Alguien sabe qué diablos está pasando aquí? He invertido demasiado tiempo en este tema, y ​​realmente necesito solucionarlo adecuadamente para seguir adelante. Supongo que me falta algo del conocimiento fundamental del puntero C, pero a mí el código parece correcto. Podría ser desde el incremento del búfer, pero hasta donde yo sé, sizeof(unsigned char) == 1, por lo que debería pasar por cada byte uno por uno.

Por lo que vale, el código funciona incluso con optimizaciones en GCC en mi cuadro de Linux.

Entonces ... ¿cuál es el problema aquí? ¡Gracias!

Conforme a lo solicitado, la salida de montaje de todo el programa:

Con O2: clicky

Con -O3: clicky

que observar este comportamiento en GCC 4.6.2 (que se ejecuta con MinGW)

+3

¿Qué hay de usar 'O2'? 'O3' es bastante arriesgado. SIGILL significa - señal, causada por instrucción ilegal. Parece un error de compilación para mí. O me estoy perdiendo algo. –

+1

Esto funciona perfecto en mi gcc 4.4.3. Si nadie nota nada, puede intentar mostrarnos el código de ensamblaje, producido por gcc, en ambos casos - con 'O3' y' O2' (si funciona bien; de lo contrario, menor nivel de optimización) –

+0

@KirilKirov Este error fue descubierto originalmente en una biblioteca de alto rendimiento, por lo que bajar a O2 no es realmente la mejor solución para nosotros; por supuesto, si se trata de un error del compilador bajaremos a O2 hasta que se emita una solución. Publicaré el ensamblaje producido sin optimizaciones y con -O3. – Thomas

Respuesta

8

Desde mi comentario:

Ma Asegúrese de que el compilador tenga la información correcta sobre la arquitectura de destino. Al leer la salida -O3, parece que el compilador está configurando la optimización SIMD, de hecho está haciendo que el código sea más paralelo usando instrucciones vectoriales (como movdqa). Si el procesador de destino no coincide con el 100% para lo que el compilador está emitiendo el código, puede terminar con instrucciones ilegales.

+0

Gracias, eso fue todo (ver mi propia "respuesta de extensión" para más detalles). – Thomas

8

Estoy agregando esto como una extensión de la respuesta de Unwind (que acepto porque me puso en el camino correcto).

Después de revisar el código optimizado, noté las instrucciones de AVX. Al principio, pensé que no debería causar un problema, teniendo en cuenta que mi procesador es compatible con el conjunto de instrucciones AVX. Sin embargo, resulta que hay dos versiones distintas de AVX: AVX1 y AVX2. Y, aunque mi procesador solo es compatible con AVX1, gcc usa indiscriminadamente códigos de operación AVX2, siempre y cuando el procesador admita cualquiera de las dos versiones (llvm cometió el mismo error, hay bug reports en eso). Esto es, por lo que puedo concebir, una operación incorrecta y un error de compilación.

El resultado es código AVX2 en un sistema AVX1, lo que obviamente conduce a una instrucción ilegal.Explica muchas cosas, desde el código que no falla en las entradas menores de 32 bytes (debido al ancho de registro de 256 bits), hasta el código que funciona en mi cuadro Linux, que es una máquina virtual con soporte de CPU limitado a SSE3.

La solución es desactivar -O3 y volver a -O2, donde gcc no recurrirá a las instrucciones más estrictas de SIMD para optimizar el código simple, o usar la palabra clave volatile que lo forzará a pasar por el tampones byte por byte, con esmero, así:

*(unsigned char volatile *)dst ^= *(unsigned char volatile *)src; 

Esto es por supuesto muy lento y probablemente peor que la utilización del O2 (ignorando las repercusiones de todo el programa), pero se puede evitar pasando a través de la memoria intermedia int por int y relleno al final, que es lo suficientemente bueno en términos de velocidad.

Otra buena solución es actualizar a una versión de gcc que no tiene este error (esta versión puede no existir todavía, no he comprobado).

EDIT: la solución final es tirar la bandera -mno-AVX en GCC, desactivando así cualquier y todos los códigos de operación AVX, anulando por completo el error sin modificaciones de código (y se puede quitar fácilmente una vez a la versión del compilador es parcheado disponible).

Qué error del compilador perverso.

+0

+1 para contar es la historia completa; Felicitaciones por cazar esta pieza de extrañeza. –