2011-08-26 11 views
6

Estoy haciendo algo de experimentación con el ensamblaje x86-64. Después de haber recopilado esta función dummy:gcc argument register derrame en x86-64

long myfunc(long a, long b, long c, long d, 
      long e, long f, long g, long h) 
{ 
    long xx = a * b * c * d * e * f * g * h; 
    long yy = a + b + c + d + e + f + g + h; 
    long zz = utilfunc(xx, yy, xx % yy); 
    return zz + 20; 
} 

Con gcc -O0 -g Me sorprendió encontrar lo siguiente en el inicio del montaje de la función:

0000000000400520 <myfunc>: 
    400520:  55      push rbp 
    400521:  48 89 e5    mov rbp,rsp 
    400524:  48 83 ec 50    sub rsp,0x50 
    400528:  48 89 7d d8    mov QWORD PTR [rbp-0x28],rdi 
    40052c:  48 89 75 d0    mov QWORD PTR [rbp-0x30],rsi 
    400530:  48 89 55 c8    mov QWORD PTR [rbp-0x38],rdx 
    400534:  48 89 4d c0    mov QWORD PTR [rbp-0x40],rcx 
    400538:  4c 89 45 b8    mov QWORD PTR [rbp-0x48],r8 
    40053c:  4c 89 4d b0    mov QWORD PTR [rbp-0x50],r9 
    400540:  48 8b 45 d8    mov rax,QWORD PTR [rbp-0x28] 
    400544:  48 0f af 45 d0   imul rax,QWORD PTR [rbp-0x30] 
    400549:  48 0f af 45 c8   imul rax,QWORD PTR [rbp-0x38] 
    40054e:  48 0f af 45 c0   imul rax,QWORD PTR [rbp-0x40] 
    400553:  48 0f af 45 b8   imul rax,QWORD PTR [rbp-0x48] 
    400558:  48 0f af 45 b0   imul rax,QWORD PTR [rbp-0x50] 
    40055d:  48 0f af 45 10   imul rax,QWORD PTR [rbp+0x10] 
    400562:  48 0f af 45 18   imul rax,QWORD PTR [rbp+0x18] 

gcc derrames de forma muy extraña de todo argumento registra en la pila y luego toma ellos de la memoria para otras operaciones.

Esto solo ocurre en -O0 (con -O1 no hay problemas), pero aún así, ¿por qué? Esto parece una anti-optimización para mí, ¿por qué gcc haría eso?

+6

Creo que podría tenerlo al revés. Estoy bastante seguro de que lo anterior es cómo GCC siempre (inicialmente) genera el código, es solo que normalmente no lo verás, ya que está optimizado de forma trivial (pero, por supuesto, solo si las optimizaciones están habilitadas). – user786653

+0

Esto no es anti optimización, simplemente no es optimización. – hirschhornsalz

+0

Acabo de ver este ejemplo en alguna parte: http://eli.thegreenplace.net/2011/09/06/stack-frame-layout-on-x86-64/ :-) –

Respuesta

7

No soy de ninguna manera un experto interno de GCC, pero le daré una oportunidad. Desafortunadamente, la mayoría de la información sobre los GCC registra la asignación y el desbordamiento parece estar desactualizado (haciendo referencia a archivos como local-alloc.c que ya no existen).

Estoy viendo el código fuente de gcc-4.5-20110825.

En GNU C Compiler Internals se menciona que el código de función inicial es generado por expand_function_start en gcc/function.c. Allí nos encontramos con lo siguiente para los parámetros de manejo:

4462 /* Initialize rtx for parameters and local variables. 
4463  In some cases this requires emitting insns. */ 
4464 assign_parms (subr); 

En assign_parms el código que se encarga de dónde se almacena cada argumentos es la siguiente:

3207  if (assign_parm_setup_block_p (&data)) 
3208   assign_parm_setup_block (&all, parm, &data); 
3209  else if (data.passed_pointer || use_register_for_decl (parm)) 
    assign_parm_setup_reg (&all, parm, &data); 
3211  else 
3212   assign_parm_setup_stack (&all, parm, &data); 

maneja tipos de datos agregados y no es aplicable en este caso y dado que los datos no se pasan como un puntero, GCC comprueba use_register_for_decl.

Aquí la parte pertinente es:

1972 if (optimize) 
1973  return true; 
1974 
1975 if (!DECL_REGISTER (decl)) 
1976  return false; 

DECL_REGISTER comprueba si la variable se declara con la palabra clave register. Y ahora tenemos nuestra respuesta: la mayoría de los parámetros viven en la pila cuando las optimizaciones no están habilitadas, y luego son manejados por assign_parm_setup_stack. La ruta tomada a través del código fuente antes de que termine derramando el valor es un poco más complicada para los argumentos del puntero, pero puede rastrearse en el mismo archivo si tiene curiosidad.

¿Por qué GCC derrama todos los argumentos y variables locales con optimizaciones desactivadas? Para ayudar a la depuración. Considere esta función simple:

1 extern int bar(int); 
2 int foo(int a) { 
3   int b = bar(a | 1); 
4   b += 42; 
5   return b; 
6 } 

Compilado con gcc -O1 -c esto genera el siguiente en mi máquina:

0: 48 83 ec 08    sub $0x8,%rsp 
4: 83 cf 01    or  $0x1,%edi 
7: e8 00 00 00 00   callq c <foo+0xc> 
c: 83 c0 2a    add $0x2a,%eax 
f: 48 83 c4 08    add $0x8,%rsp 
13: c3      retq 

Lo cual está bien, excepto si se rompe en la línea 5 y intenta imprimir el valor de una, se llegar

(gdb) print a 
$1 = <value optimized out> 

a medida que el argumento se sobreescribe, ya que no se usa después de la llamada a bar.

6

Un par de razones:

  1. En el caso general, un argumento a una función tiene que ser tratado como una variable local, ya que se podría almacenar o haber tomado su dirección dentro de la función. Por lo tanto, es más simple asignar una ranura de pila para cada argumento.
  2. La información de depuración se vuelve mucho más simple de emitir con ubicaciones de pila: el valor del argumento siempre está en una ubicación específica, en lugar de moverse entre los registros y la memoria.

Cuando mira el código -O0 en general, considere que las principales prioridades del compilador son reducir el tiempo de compilación tanto como sea posible y generar información de depuración de alta calidad.

+1

Sí, y sin optimizaciones, el compilador específicamente hace que todas las líneas sean independientes, siempre recargando desde variables reales y almacenando inmediatamente, lo que le permite mover la CPU a otra línea, o cambiar el valor de cualquier variable en el depurador, y hacer que se comporte correctamente. – doug65536