Eliminando el .cfi
directivas, etiquetas no utilizadas y líneas de comentarios es un problema resuelto:los scripts detrás del explorador del compilador de Matt Godbolt son de código abierto en su proyecto github. Incluso puede resaltar el color para hacer coincidir las líneas fuente con las líneas asm (usando la información de depuración).
Puede configurarlo localmente para que pueda alimentarlo con archivos que son parte de su proyecto con todos los #include
rutas y así sucesivamente (usando -I/...
). Y puede usarlo en código fuente privado que no desea enviar a través de Internet.
Charla CppCon2017 de Matt Godbolt “¿Qué ha hecho mi compilador por mí últimamente? Desatornillar la tapa del compilador” muestra cómo usarlo (se explica por sí mismo, pero tiene algunas características interesantes si lee los documentos en github), y también cómo leer x86 asm , con una introducción suave a x86 asm para principiantes totales, y para ver la salida del compilador. Continúa mostrando algunas optimizaciones ordenadas del compilador (por ejemplo, para dividir por una constante), y qué tipo de funciones brindan una salida útil de ASM para ver la salida optimizada del compilador (argumentos de función, no int a = 123;
).
Con gcc/clang normal (no g++), -fno-asynchronous-unwind-tables
evita .cfi
directivas. Posiblemente también útil:-fno-exceptions -fno-rtti
-masm=intel
. Asegúrate de omitir -g
.
Copia/pega 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
¡Pero realmente, recomendaría simplemente usar Godbolt directamente (en línea o configurarlo localmente)! Puede alternar rápidamente entre las versiones de gcc y clang para ver si los compiladores antiguos o nuevos 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 donde int
es más ancho que un registro, o no es de 32 bits. O en un RISC frente a x86).
Para C en lugar de C++, use -xc -std=gnu11
o algo; el sitio del explorador del compilador solo proporciona g ++ / clang ++, no gcc / clang. (O puede usar el modo C en el menú desplegable de idioma, pero eso tiene una selección diferente de compiladores que en su mayoría es más limitada. Y restablece su panel de código fuente, por lo que es más difícil alternar entre C y C ++).
Opciones de compilador útiles para hacer asm para consumo humano :
-
Recuerde, su código solo tiene que compilar, no vincular:pasar un puntero a una función externa como
void ext(int*p)
es una buena manera de evitar que algo se optimice . Solo necesita un prototipo para él, sin definición, por lo que el compilador no puede alinearlo ni hacer suposiciones sobre lo que hace. -
Recomiendo usar
-O3 -Wall -Wextra -fverbose-asm -march=haswell
) para mirar el código. (-fverbose-asm
Sin embargo, puede hacer que la fuente parezca ruidosa cuando todo lo que obtiene son temporales numerados como nombres para los operandos). Cuando está jugando con la fuente para ver cómo cambia el asm, definitivamente desea que las advertencias del compilador estén habilitadas. 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 querrá mirar a la persona que llama y a la persona a la que llama sin incluirlos .
Puedes usar
__attribute__((noinline,noclone)) foo_t foo(bar_t x) { ... }
en una definición, o compilar congcc -O3 -fno-inline-functions -fno-inline-functions-called-once -fno-inline-small-functions
para deshabilitar la inserción. (Pero esas opciones de línea de comando no deshabilitan la clonación de una función para la propagación constante). Consulte Desde la perspectiva del compilador, ¿cómo se trata la referencia para la matriz y por qué no se permite pasar por valor (no decaer)? para un ejemplo.O si solo 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 en línea. Esto funciona con cualquier compilador.
-
-ffast-math
obtendrá muchas funciones libm en línea, algunas en una sola instrucción (especialmente con SSE4 disponible pararoundsd
). Algunos estarán en línea con solo-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 manera segura ninguno de los-ffast-math
en su compilación regular, tal vez tenga una idea de un cambio seguro que puede hacer en la fuente para permitir la misma optimización sin-ffast-math
. -
-O3 -fno-tree-vectorize
se optimizará sin vectorizar automáticamente , por lo que puede obtener una optimización completa sin si desea comparar con-O2
(que no habilita la autovectorización en gcc, pero sí en clang). -
clang desenrolla bucles de forma predeterminada, por lo que
-fno-unroll-loops
puede ser útil en funciones complejas . Puede tener una idea de "lo que hizo el compilador" sin tener que pasar por los bucles desenrollados. (gcc habilita-funroll-loops
con-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). -
Habilite definitivamente algún nivel de optimización, a menos que desee saber específicamente qué
-O0
hizo . Su requisito de "comportamiento de depuración predecible" hace que el compilador almacene/recargue todo entre cada declaración de C, por lo que puede modificar las variables de 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 hizo eso en la fuente C.-O0
la salida es tan ruidosa con las tiendas/recargas (y tan 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 source y asm , usa gcc -Wa,-adhln -c -g foo.c | less
para 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á allí directamente, no como un comentario del ensamblador. Así que no lo llames .s
. Un .lst
podrí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 hay varios elementos no contiguos. Las instrucciones de asm provienen de la misma línea fuente. No he usado ese comando de listado de gcc en absoluto, así que no sé qué tan bien lo hace y qué tan fácil es verlo a simple vista, en ese caso.
Me gusta la alta densidad de código del panel asm de godbolt, así que no creo que me gustaría tener líneas fuente mezcladas. Al menos no para funciones simples. Tal vez con una función que era demasiado compleja para controlar la estructura general de lo que hace el asm...
Y recuerda, cuando solo quieras mirar el asm, omite el main()
y las constantes de tiempo de compilación . Desea ver el código para tratar con una función arg en un registro, no para el código después de que la propagación constante lo convierta en return 42
, o al menos optimiza algunas cosas.
Eliminando static
y/o inline
from functions producirá una definición independiente para ellos, así como una definición para cualquier persona que llame, por lo que puede mirar eso.
No pongas tu código en una función llamada main()
. gcc sabe que main
es 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 hiciste un main()
, puede ejecutarlo y usar un depurador. stepi
(si
) pasos por instrucción. Consulte la parte inferior de la wiki de etiquetas x86 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 que esté en línea. 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_1234
o algo en la salida de asm. Puedes usar __attribute__((noclone))
para deshabilitar eso también).
Por ejemplo
Si quieres ver cómo el compilador multiplica dos enteros:pongo el siguiente código en el explorador del compilador Godbolt para obtener el asm (de gcc -O3 -march=haswell -fverbose-asm
) para la forma 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 elaboró a mano copiando y pegando la salida de asm de godbolt en el lugar correcto. Considero que es una buena forma de mostrar cómo se compila una función corta en respuestas SO/informes de errores del compilador/correos electrónicos).
Siempre puede mirar el ensamblado generado desde el archivo de objeto, en lugar de usar la salida del ensamblado de los compiladores. objdump
me viene a la mente.
Incluso puedes decirle a objdump
para mezclar la fuente con el ensamblado, lo que facilita averiguar 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 objdump
banderas:
-d
desensambla todas las secciones ejecutables-S
entremezcla ensamblado con código fuente (-g
requerido al compilar cong++
)-M intel
elige la sintaxis de Intel sobre la fea sintaxis de AT&T (opcional )
Me gusta insertar etiquetas que puedo extraer fácilmente de la salida de objdump.
int main() {
asm volatile ("interesting_part_begin%=:":);
do_something();
asm volatile ("interesting_part_end%=:":);
}
Todavía no he tenido ningún problema con esto, pero asm volatile
puede ser muy difícil para el optimizador de un compilador porque tiende a dejar tal código intacto.