c++ compiler-construction clang

c++ - ¿Por qué los compiladores duplican algunas instrucciones?



compiler-construction clang (2)

A veces, los compiladores generan código con duplicaciones de instrucciones extrañas que se pueden eliminar de forma segura. Considere la siguiente pieza de código:

int gcd(unsigned x, unsigned y) { return x == 0 ? y : gcd(y % x, x); }

Aquí está el código de ensamblaje (generado por clang 5.0 con optimizaciones habilitadas):

gcd(unsigned int, unsigned int): # @gcd(unsigned int, unsigned int) mov eax, esi mov edx, edi test edx, edx je .LBB0_1 .LBB0_2: # =>This Inner Loop Header: Depth=1 mov ecx, edx xor edx, edx div ecx test edx, edx mov eax, ecx jne .LBB0_2 mov eax, ecx ret .LBB0_1: ret

En el siguiente fragmento:

mov eax, ecx jne .LBB0_2 mov eax, ecx

Si el salto no ocurre, eax se reasigna sin ninguna razón obvia.

El otro ejemplo son dos ret''s al final de la función: uno funcionaría perfectamente también.

¿El compilador simplemente no es lo suficientemente inteligente o hay una razón para no eliminar las duplicaciones?


Cualquier compilador tendrá un montón de transformaciones para cambiar el nombre de registro, desenrollar, izar, etc. La combinación de sus resultados puede conducir a casos subóptimos, como lo que ha mostrado. Marc Glisse ofrece buenos consejos: vale la pena informar de un error. Está describiendo una oportunidad para que un optimizador de mirillas descarte instrucciones que

  • no afecta el estado de los registros y la memoria en absoluto, o
  • no afecta el estado que importa para las condiciones posteriores de una función, no importará para su API pública.

Suena como una oportunidad para las técnicas de ejecución simbólica . Si el solucionador de restricciones no encuentra puntos de ramificación para un MOV determinado, tal vez sea realmente un NOP.


Los compiladores pueden realizar optimizaciones que no son obvias para las personas y eliminar las instrucciones no siempre agiliza las cosas.

Una pequeña cantidad de búsqueda muestra que varios procesadores AMD tienen problemas de predicción de rama cuando un RET está inmediatamente después de una rama condicional. Al llenar ese espacio con lo que esencialmente no funciona, se evita el problema de rendimiento.

Actualizar:

Ejemplo de referencia, la sección 6.2 de la "Guía de optimización de software para procesadores AMD64" (consulte http://support.amd.com/TechDocs/25112.PDF ) dice:

Específicamente, evite las siguientes dos situaciones:

  • Cualquier tipo de rama (ya sea condicional o incondicional) que tenga como objetivo la instrucción RET de retorno cercano de un solo byte. Ver "Ejemplos".

  • Una rama condicional que ocurre en el código directamente antes de la instrucción RET de retorno cercano de un solo byte.

También entra en detalles sobre por qué los objetivos de salto deben tener una alineación, lo que también puede explicar los RET duplicados al final de la función.