¿Cómo eliminar el "ruido" de la salida del ensamblaje GCC/clang?

Resuelto m.s. asked hace 8 años • 3 respuestas

Quiero inspeccionar el resultado del ensamblaje de la aplicación.boost::variant en mi código para ver qué llamadas intermedias están optimizadas.

Cuando compilo el siguiente ejemplo (con GCC 5.3 usando g++ -O3 -std=c++14 -S), parece como si el compilador optimizara todo y devolviera directamente 100:

(...)
main:
.LFB9320:
    .cfi_startproc
    movl    $100, %eax
    ret
    .cfi_endproc
(...)

#include <boost/variant.hpp>

struct Foo
{
    int get() { return 100; }
};

struct Bar
{
    int get() { return 999; }
};

using Variant = boost::variant<Foo, Bar>;


int run(Variant v)
{
    return boost::apply_visitor([](auto& x){return x.get();}, v);
}
int main()
{
    Foo f;
    return run(f);
}

Sin embargo, el resultado completo del ensamblado contiene mucho más que el extracto anterior, que a mí me parece que nunca se llama. ¿Hay alguna manera de decirle a GCC/clang que elimine todo ese "ruido" y simplemente genere lo que realmente se llama cuando se ejecuta el programa?


salida de ensamblaje completo:

    .file   "main1.cpp"
    .section    .rodata.str1.8,"aMS",@progbits,1
    .align 8
.LC0:
    .string "/opt/boost/include/boost/variant/detail/forced_return.hpp"
    .section    .rodata.str1.1,"aMS",@progbits,1
.LC1:
    .string "false"
    .section    .text.unlikely._ZN5boost6detail7variant13forced_returnIvEET_v,"axG",@progbits,_ZN5boost6detail7variant13forced_returnIvEET_v,comdat
.LCOLDB2:
    .section    .text._ZN5boost6detail7variant13forced_returnIvEET_v,"axG",@progbits,_ZN5boost6detail7variant13forced_returnIvEET_v,comdat
.LHOTB2:
    .p2align 4,,15
    .weak   _ZN5boost6detail7variant13forced_returnIvEET_v
    .type   _ZN5boost6detail7variant13forced_returnIvEET_v, @function
_ZN5boost6detail7variant13forced_returnIvEET_v:
.LFB1197:
    .cfi_startproc
    subq    $8, %rsp
    .cfi_def_cfa_offset 16
    movl    $_ZZN5boost6detail7variant13forced_returnIvEET_vE19__PRETTY_FUNCTION__, %ecx
    movl    $49, %edx
    movl    $.LC0, %esi
    movl    $.LC1, %edi
    call    __assert_fail
    .cfi_endproc
.LFE1197:
    .size   _ZN5boost6detail7variant13forced_returnIvEET_v, .-_ZN5boost6detail7variant13forced_returnIvEET_v
    .section    .text.unlikely._ZN5boost6detail7variant13forced_returnIvEET_v,"axG",@progbits,_ZN5boost6detail7variant13forced_returnIvEET_v,comdat
.LCOLDE2:
    .section    .text._ZN5boost6detail7variant13forced_returnIvEET_v,"axG",@progbits,_ZN5boost6detail7variant13forced_returnIvEET_v,comdat
.LHOTE2:
    .section    .text.unlikely._ZN5boost6detail7variant13forced_returnIiEET_v,"axG",@progbits,_ZN5boost6detail7variant13forced_returnIiEET_v,comdat
.LCOLDB3:
    .section    .text._ZN5boost6detail7variant13forced_returnIiEET_v,"axG",@progbits,_ZN5boost6detail7variant13forced_returnIiEET_v,comdat
.LHOTB3:
    .p2align 4,,15
    .weak   _ZN5boost6detail7variant13forced_returnIiEET_v
    .type   _ZN5boost6detail7variant13forced_returnIiEET_v, @function
_ZN5boost6detail7variant13forced_returnIiEET_v:
.LFB9757:
    .cfi_startproc
    subq    $8, %rsp
    .cfi_def_cfa_offset 16
    movl    $_ZZN5boost6detail7variant13forced_returnIiEET_vE19__PRETTY_FUNCTION__, %ecx
    movl    $39, %edx
    movl    $.LC0, %esi
    movl    $.LC1, %edi
    call    __assert_fail
    .cfi_endproc
.LFE9757:
    .size   _ZN5boost6detail7variant13forced_returnIiEET_v, .-_ZN5boost6detail7variant13forced_returnIiEET_v
    .section    .text.unlikely._ZN5boost6detail7variant13forced_returnIiEET_v,"axG",@progbits,_ZN5boost6detail7variant13forced_returnIiEET_v,comdat
.LCOLDE3:
    .section    .text._ZN5boost6detail7variant13forced_returnIiEET_v,"axG",@progbits,_ZN5boost6detail7variant13forced_returnIiEET_v,comdat
.LHOTE3:
    .section    .text.unlikely,"ax",@progbits
.LCOLDB4:
    .text
.LHOTB4:
    .p2align 4,,15
    .globl  _Z3runN5boost7variantI3FooJ3BarEEE
    .type   _Z3runN5boost7variantI3FooJ3BarEEE, @function
_Z3runN5boost7variantI3FooJ3BarEEE:
.LFB9310:
    .cfi_startproc
    subq    $8, %rsp
    .cfi_def_cfa_offset 16
    movl    (%rdi), %eax
    cltd
    xorl    %edx, %eax
    cmpl    $19, %eax
    ja  .L7
    jmp *.L9(,%rax,8)
    .section    .rodata
    .align 8
    .align 4
.L9:
    .quad   .L30
    .quad   .L10
    .quad   .L7
    .quad   .L7
    .quad   .L7
    .quad   .L7
    .quad   .L7
    .quad   .L7
    .quad   .L7
    .quad   .L7
    .quad   .L7
    .quad   .L7
    .quad   .L7
    .quad   .L7
    .quad   .L7
    .quad   .L7
    .quad   .L7
    .quad   .L7
    .quad   .L7
    .quad   .L7
    .text
    .p2align 4,,10
    .p2align 3
.L7:
    call    _ZN5boost6detail7variant13forced_returnIiEET_v
    .p2align 4,,10
    .p2align 3
.L30:
    movl    $100, %eax
.L8:
    addq    $8, %rsp
    .cfi_remember_state
    .cfi_def_cfa_offset 8
    ret
    .p2align 4,,10
    .p2align 3
.L10:
    .cfi_restore_state
    movl    $999, %eax
    jmp .L8
    .cfi_endproc
.LFE9310:
    .size   _Z3runN5boost7variantI3FooJ3BarEEE, .-_Z3runN5boost7variantI3FooJ3BarEEE
    .section    .text.unlikely
.LCOLDE4:
    .text
.LHOTE4:
    .globl  _Z3runN5boost7variantI3FooI3BarEEE
    .set    _Z3runN5boost7variantI3FooI3BarEEE,_Z3runN5boost7variantI3FooJ3BarEEE
    .section    .text.unlikely
.LCOLDB5:
    .section    .text.startup,"ax",@progbits
.LHOTB5:
    .p2align 4,,15
    .globl  main
    .type   main, @function
main:
.LFB9320:
    .cfi_startproc
    movl    $100, %eax
    ret
    .cfi_endproc
.LFE9320:
    .size   main, .-main
    .section    .text.unlikely
.LCOLDE5:
    .section    .text.startup
.LHOTE5:
    .section    .rodata
    .align 32
    .type   _ZZN5boost6detail7variant13forced_returnIvEET_vE19__PRETTY_FUNCTION__, @object
    .size   _ZZN5boost6detail7variant13forced_returnIvEET_vE19__PRETTY_FUNCTION__, 58
_ZZN5boost6detail7variant13forced_returnIvEET_vE19__PRETTY_FUNCTION__:
    .string "T boost::detail::variant::forced_return() [with T = void]"
    .align 32
    .type   _ZZN5boost6detail7variant13forced_returnIiEET_vE19__PRETTY_FUNCTION__, @object
    .size   _ZZN5boost6detail7variant13forced_returnIiEET_vE19__PRETTY_FUNCTION__, 57
_ZZN5boost6detail7variant13forced_returnIiEET_vE19__PRETTY_FUNCTION__:
    .string "T boost::detail::variant::forced_return() [with T = int]"
    .ident  "GCC: (Ubuntu 5.3.0-3ubuntu1~14.04) 5.3.0 20151204"
    .section    .note.GNU-stack,"",@progbits
m.s. avatar Jul 24 '16 19:07 m.s.
Aceptado

Eliminar las .cfidirectivas, las etiquetas no utilizadas y las líneas de comentarios es un problema resuelto: los scripts detrás del explorador de compiladores de Matt Godbolt son de código abierto en su proyecto github . Incluso puede resaltar colores para hacer coincidir las líneas de origen con las líneas de ensamblaje (usando la información de depuración).

Puede configurarlo localmente para poder alimentarlo con archivos que forman parte de su proyecto con todas las #includerutas, etc. (usando -I/...). Y entonces puedes usarlo en código fuente privado que no deseas enviar a través de Internet.

Charla CppCon2017 de Matt Godbolt “¿Qué ha hecho mi compilador por mí últimamente? Unbolting the Compiler's Lid” muestra cómo usarlo (se explica por sí mismo, pero tiene algunas características interesantes si lees los documentos en github), y también cómo leer x86 asm , con una suave introducción al propio x86 asm para principiantes. y mirar la salida del compilador. Continúa mostrando algunas optimizaciones interesantes del compilador (por ejemplo, para dividir por una constante) y qué tipo de funciones dan resultados ASM útiles para observar los resultados optimizados del compilador (argumentos de función, no int a = 123;).

En el explorador del compilador Godbolt, puede ser útil usarlo -g0 -fno-asynchronous-unwind-tablessi desea desmarcar la opción de filtro para directivas, por ejemplo, porque desea ver .sectiony .p2aligncosas en la salida del compilador. El valor predeterminado es agregar -ga sus opciones para obtener la información de depuración que utiliza para resaltar con colores las líneas fuente y de ensamblaje coincidentes, pero esto significa .cfidirectivas para cada operación de pila y .locpara cada línea fuente, entre otras cosas.


Con gcc/clang simple (no g++), -fno-asynchronous-unwind-tablesevita .cfidirectivas. Posiblemente también sea útil: -fno-exceptions -fno-rtti -masm=intel. Asegúrate de omitir -g.

Copie/pegue esto para uso local :

g++ -fno-asynchronous-unwind-tables -fno-exceptions -fno-rtti -fverbose-asm \
    -Wall -Wextra  foo.cpp   -O3 -masm=intel -S -o- | less

O -Ospuede ser más legible, por ejemplo, usar divpara la división por constantes que no son potencias de 2 en lugar de un inverso multiplicativo, aunque eso es mucho peor para el rendimiento y solo un poco más pequeño, en todo caso.

En Godbolt, utilícelo-g0 si desea desmarcar "directivas de filtro" para ver secciones y demás, o variables globales que filtró incorrectamente. Su valor predeterminado -gpuede desordenar las cosas, por lo que -g0lo contrarresta. ( -g0es el valor predeterminado sin opciones de línea de comandos). Utilice también -fno-asynchronous-unwind-tables, tal vez también -fno-exceptions -fno-rtti.


¡Pero en realidad, recomendaría usar Godbolt directamente (en línea o configurarlo localmente)! Puede cambiar rápidamente entre versiones de gcc y clang para ver si los compiladores nuevos o antiguos hacen algo tonto. (O lo que hace ICC, o incluso lo que hace MSVC). Incluso hay ARM / ARM64 gcc 6.3 y varios gcc para PowerPC, MIPS, AVR, MSP430. (Puede ser interesante ver qué sucede en una máquina que intes más ancha que un registro o que no es de 32 bits. O en un RISC frente a x86).

Para C en lugar de C++, puede utilizar -xc -std=gnu11para evitar cambiar el menú desplegable de idioma a C, lo que restablece el panel de origen y las opciones del compilador, y tiene un conjunto diferente de compiladores disponibles.


Opciones de compilación útiles para crear ensamblaje para consumo humano :

  • Recuerde, su código solo tiene que compilarse, no vincularse: pasar un puntero a una función externa como void ext(void*p)es una buena manera de evitar que algo se optimice . Solo necesita un prototipo, sin definición, por lo que el compilador no puede incluirlo ni hacer suposiciones sobre lo que hace. (O el tipo asm en líneaBenchmark::DoNotOptimize puede obligar a un compilador a materializar un valor en un registro, u olvidarse de que sea una constante conocida, si conoce la sintaxis asm en línea de GNU C lo suficientemente bien como para usar restricciones para comprender el efecto que está teniendo en lo que están exigiendo al compilador.)

  • Recomiendo usarlo -O3 -Wall -Wextra -fverbose-asm -march=haswellpara mirar el código. ( -fverbose-asmSin embargo, puede hacer que la fuente parezca ruidosa cuando todo lo que obtiene son temporales numerados como nombres para los operandos). Cuando esté jugueteando con la fuente para ver cómo cambia el conjunto, definitivamente querrá habilitar las advertencias del compilador. No querrás perder el tiempo rascándote la cabeza cuando la explicación es que hiciste algo que merece una advertencia en la fuente.

  • Para ver cómo funciona la convención de llamadas, a menudo conviene observar a la persona que llama y al destinatario sin incluirlas .

    Puede usarlo __attribute__((noipa)) foo_t foo(bar_t x) { ... }en una definición o compilar con gcc -O3 -fno-inline-functions -fno-inline-functions-called-once -fno-inline-small-functionspara deshabilitar la inserción. (Pero esas opciones de línea de comando no deshabilitan la clonación de una función para propagación constante. noipa= no hay análisis entre procedimientos. Es incluso más fuerte que __attribute__((noinline,noclone)).) Consulte Desde la perspectiva del compilador, cómo se maneja la referencia para la matriz y por qué pasar por valor. (no descomposición) ¿no está permitido? para un ejemplo.

    O si simplemente desea ver cómo las funciones pasan/reciben argumentos de diferentes tipos, puede usar nombres diferentes pero el mismo prototipo para que el compilador no tenga una definición para incorporar. Esto funciona con cualquier compilador. Sin una definición, una función es solo un cuadro negro para el optimizador, gobernado únicamente por la convención de llamada/ABI.

  • -ffast-mathobtendrá muchas funciones libm en línea, algunas en una sola instrucción (especialmente con SSE4 disponible para roundsd). Algunos se alinearán solo con -fno-math-errno, u otras partes "más seguras" de -ffast-math, sin las partes que permiten al compilador redondear de manera diferente. Si tiene código FP, definitivamente mírelo con/sin -ffast-math. Si no puede habilitar de forma segura ninguno de -ffast-mathen su compilación habitual, tal vez tenga una idea de un cambio seguro que pueda realizar en el código fuente para permitir la misma optimización sin -ffast-math.

  • -O3 -fno-tree-vectorizese optimizará sin auto-vectorización , por lo que puede obtener una optimización completa sin si desea comparar -O2(que no habilita la autovectorización en gcc11 y versiones anteriores, pero sí en todos los clang).

    -Os(optimizar tamaño y velocidad) puede resultar útil para mantener el código más compacto, lo que significa menos código para comprender. clang -Ozoptimiza el tamaño incluso cuando perjudica la velocidad, incluso usando push 1/ pop raxen lugar de mov eax, 1, por lo que solo es interesante para code golf .

    Incluso -Og(optimización mínima) podría ser lo que quieras ver, dependiendo de tus objetivos. -O0está lleno de ruido de almacenamiento/recarga, lo que hace que sea más difícil de seguir, a menos que uses registervars . La única ventaja es que cada instrucción C se compila en un bloque de instrucciones separado y permite -fverbose-asmusar los nombres reales de las var C.

  • clang desenrolla bucles de forma predeterminada, por lo que -fno-unroll-loopspuede resultar útil en funciones complejas . Puede tener una idea de "lo que hizo el compilador" sin tener que recorrer los bucles desenrollados. (gcc habilita -funroll-loopscon -fprofile-use, pero no con -O3). (Esta es una sugerencia para código legible por humanos, no para código que se ejecutaría más rápido).

  • Definitivamente habilite algún nivel de optimización, a menos que desee saber específicamente qué -O0hizo . Su requisito de "comportamiento de depuración predecible" hace que el compilador almacene/recargue todo entre cada instrucción C, por lo que puede modificar las variables C con un depurador e incluso "saltar" a una línea fuente diferente dentro de la misma función, y hacer que la ejecución continúe como si Hice eso en la fuente C. -O0la salida es muy ruidosa con las tiendas/recargas (y muy lenta) no solo por la falta de optimización, sino también por la desoptimización forzada para admitir la depuración . (también relacionado ).


Para obtener una combinación de fuente y asm , use gcc -Wa,-adhln -c -g foo.c | lesspara pasar opciones adicionales a as. (Más discusión sobre esto en una publicación de blog y en otro blog ). Tenga en cuenta que la salida de esto no es una entrada válida del ensamblador, porque la fuente C está ahí directamente, no como un comentario del ensamblador. Así que no lo llames .s. A .lstpodría tener sentido si desea guardarlo en un archivo.

El resaltado de color de Godbolt tiene un propósito similar y es excelente para ayudarlo a ver cuando varias instrucciones ensambladas no contiguas provienen de la misma línea fuente. No he usado ese comando de listado de gcc en absoluto, así que IDK qué tan bien funciona y qué fácil es para el ojo verlo, en ese caso.

Me gusta la alta densidad de código del panel de ensamblaje de Godbolt, así que no creo que me guste tener líneas fuente mezcladas. Al menos no para funciones simples. Tal vez con una función que era demasiado compleja para entender la estructura general de lo que hace el conjunto...


Y recuerde, cuando quiera simplemente mirar el conjunto, omita las main()constantes de tiempo de compilación y . Desea ver el código para tratar con una función arg en un registro, no el código después de que la propagación constante lo convierte en return 42, o al menos optimiza algunas cosas.

Quitar staticy/o inlinede funciones producirá una definición independiente para ellas, así como una definición para cualquier persona que llame, así que puedes mirar eso.

No pongas tu código en una función llamadamain() . gcc sabe que maines especial y asume que solo se llamará una vez, por lo que lo marca como "frío" y lo optimiza menos.


La otra cosa que puedes hacer: si creaste un archivo main(), puedes ejecutarlo y usar un depurador. stepi(si ) pasos por instrucción. Ver la parte inferior delx86 etiqueta wiki para obtener instrucciones. Pero recuerde que el código podría optimizarse después de insertarlo en main con argumentos constantes en tiempo de compilación.

__attribute__((noinline))puede ayudar, en una función que no desea incluir. gcc también hará clones de funciones de propagación constante, es decir, una versión especial con uno de los argumentos como constante, para sitios de llamadas que saben que están pasando una constante. El nombre del símbolo será .clone.foo.constprop_1234o algo así en la salida del asm. También puedes usar __attribute__((noclone))para desactivar eso).


Por ejemplo

Si desea ver cómo el compilador multiplica dos números enteros: puse el siguiente código en el explorador del compilador Godbolt para obtener el asm (de gcc -O3 -march=haswell -fverbose-asm) de la manera incorrecta y la forma correcta de probar esto.

// the wrong way, which people often write when they're used to creating a runnable test-case with a main() and a printf
// or worse, people will actually look at the asm for such a main()
int constants() { int a = 10, b = 20; return a * b; }
    mov     eax, 200  #,
    ret                     # compiles the same as  return 200;  not interesting

// the right way: compiler doesn't know anything about the inputs
// so we get asm like what would happen when this inlines into a bigger function.
int variables(int a, int b) { return a * b; }
    mov     eax, edi  # D.2345, a
    imul    eax, esi        # D.2345, b
    ret

(Esta combinación de asm y C se creó a mano copiando y pegando la salida de asm de godbolt en el lugar correcto. Creo que es una buena manera de mostrar cómo se compila una función breve en respuestas SO/informes de errores del compilador/correos electrónicos).

Peter Cordes avatar Jul 24 '2016 13:07 Peter Cordes

Siempre puede ver el ensamblado generado desde el archivo objeto, en lugar de utilizar la salida del ensamblado del compilador. objdumpme viene a la mente.

Incluso puede indicar objdumpque se mezcle la fuente con el ensamblaje, lo que facilita determinar qué línea de fuente corresponde a qué instrucciones. Sesión de ejemplo:

$ cat test.cc
int foo(int arg)
{
    return arg * 42;
}

$ g++ -g -O3 -std=c++14 -c test.cc -o test.o && objdump -dS -M intel test.o

test.o:     file format elf64-x86-64


Disassembly of section .text:

0000000000000000 <_Z3fooi>:
int foo(int arg)
{
    return arg + 1;
   0:   8d 47 01                lea    eax,[rdi+0x1]
}
   3:   c3                      ret    

Explicación de objdumpbanderas:

  • -ddesmonta todas las secciones ejecutables
  • -Smezcla el ensamblaje con la fuente ( -grequerido al compilar con g++)
  • -M intelelige la sintaxis de Intel en lugar de la fea sintaxis de AT&T ( opcional )
Leandros avatar Jul 24 '2016 13:07 Leandros

Me gusta insertar etiquetas que pueda extraer fácilmente de la salida del objdump.

int main() {
    asm volatile ("interesting_part_begin%=:":);
    do_something();
    asm volatile ("interesting_part_end%=:":);
}

No he tenido ningún problema con esto todavía, pero asm volatilepuede ser muy difícil para el optimizador de un compilador porque tiende a dejar ese código intacto.

Tim avatar Jul 24 '2016 15:07 Tim