Saltar al contenido

¿Por qué rbp y rsp se denominan registros de propósito general?

Luego de mucho batallar hemos hallado el resultado de este enigma que muchos de nuestros usuarios de nuestro sitio web presentan. Si quieres aportar algún detalle no dejes de compartir tu comentario.

Solución:

Propósito general significa que todos estos registros pueden usarse con cualquier instrucción que realice cálculos con registros de propósito general, mientras que, por ejemplo, no puede hacer lo que quiera con el puntero de instrucción (RIP) o el registro de banderas (RFLAGS).

Algunos de estos registros fueron previstos para ser usados ​​para un uso específico, y comúnmente lo son. Los más críticos son el RSP y RBP.

Si necesita usarlos para su propio propósito, debe guardar su contenido antes de guardar algo más en su interior y restaurarlos a su valor original cuando haya terminado.

Si un registro puede ser un operando para add, o usado en un modo de direccionamiento, es “propósito general”, a diferencia de registros como el FS registro de segmento o RIP. Los registros GP también se denominan “registros de números enteros”, aunque otros tipos de registros también pueden contener números enteros.

En la arquitectura de la computadora, es común que las CPU manejen internamente registros / instrucciones enteros por separado de los registros / instrucciones FP / SIMD. por ejemplo, las CPU de la familia Intel Sandybridge tienen archivos de registro físico separados para cambiar el nombre de los registros GP integer vs. FP / vector. Estos se denominan simplemente archivos de registro de enteros frente a FP. (Donde FP es la abreviatura de todo lo que un kernel no necesita guardar / restaurar para usar los registros GP mientras deja intacto el estado FPU / SIMD del espacio de usuario). Cada entrada en el archivo de registro FP tiene 256 bits de ancho (para contienen un vector AVX ymm), pero las entradas del archivo de registro de enteros solo tienen que tener un ancho de 64 bits.

En las CPU que cambian el nombre de los registros de segmento (Skylake no lo hace), supongo que eso sería parte del estado entero, al igual que RFLAGS + RIP. Pero cuando decimos “registro entero”, normalmente nos referimos específicamente a un registro de propósito general.


Cada registro tiene alguna especialidad para algunas instrucciones, excepto algunos de los registros completamente nuevos agregados con x86-64: R8-R15. Estos no los descalifican como de uso general. Los (16 bajos de los) 8 originales se remontan a 8086, y hubo usos implícitos de cada uno de ellos incluso en el 8086 original.

Para RSP, es especial para push / pop / call / ret, por lo que la mayoría del código nunca lo usa para nada más. (Y en el modo kernel, se usa de forma asincrónica para las interrupciones, por lo que realmente no puede guardarlo en algún lugar para obtener un registro GP adicional de la forma en que puede hacerlo en el código de espacio de usuario: ¿ESP es tan general como EAX?)

Pero en el condicional controlado (como sin manejadores de señales) no es necesario usar RSP para un puntero de pila. por ejemplo, puede usarlo para leer un array en un bucle con pop, como en esta respuesta de código de golf. (De hecho usé esp en código de 32 bits, pero la misma diferencia: pop es más rápido que lodsd en Skylake, mientras que ambos son de 1 byte).


Usos implícitos y especialidad para cada registro:

Consulte también Ensamblado x86: por qué [e]bx conservado en convenciones de llamadas? para una lista parcial.

Principalmente estoy limitando esto a las instrucciones de espacio de usuario, especialmente a las que un compilador moderno podría emitir desde código C o C ++. No intento ser exhaustivo para las reglas que tienen muchos usos implícitos.

  • rax: un operando [i]mul / [i]div / cdq / cdqe, string instrucciones (stos), cmpxchg, etc. etc. Así como codificaciones especiales más cortas para muchas instrucciones inmediatas como 2 bytes cmp al, 1 o 5 bytes add eax, 12345 (sin byte ModRM). Consulte también codegolf.SE Consejos para jugar al golf en código máquina x86 / x64.

    También hay xchg-con-eax que es donde 0x90 nop vino de (antes nop se convirtió en una instrucción documentada por separado en x86-64, porque xchg eax,eax cero-extiende eax en RAX y, por lo tanto, no puede usar el 0x90 codificación. Pero xchg rax,raxpueden aún ensamblar a REX.W = 1 0x90.)

  • rcx: recuentos de turnos, rep-string cuenta, el lento loop instrucción
  • rdx: rdx:rax se usa para dividir y multiplicar, y cwd / cdq / cqo para configurarlos. rdtsc. IMC2 mulx.
  • rbx: 8086 xlatb. cpuid utilice los cuatro de EAX..EDX. 486 cmpxchg8b, x86-64 cmpxchg16b. La mayoría de los compiladores de 32 bits emitirán cmpxchg8 por std::atomic::compare_exchange_weak. (Pure load / pure store puede usar SSE MOVQ o x87 fild / fistp, sin embargo, si apunta a Pentium o posterior). Los compiladores de 64 bits usarán 64 bits lock cmpxchg, no cmpxchg8b.

    Algunos compiladores de 64 bits emitirán cmpxchg16b por atomic. RBX tiene la menor cantidad de usos implícitos de los 8 originales, pero lock cmpxchg16b es uno de los pocos compiladores que realmente utilizará.

  • rsi/rdi: string operaciones, incluyendo rep movsb que algunos compiladores a veces están en línea. (gcc también en línea rep cmpsb por string literales en algunos casos, pero probablemente eso no sea óptimo).
  • rbp: leave (solo 1 uop más lento que mov rsp, rbp / pop rbp. gcc en realidad lo usa en funciones con un puntero de marco, cuando no puede simplemente pop rbp). También el horriblemente lento enter que nadie usa nunca.
  • rsp: operaciones de pila: push / pop / call / ret, y leave. (Y enter). Y en modo kernel (no espacio de usuario) uso asincrónico por hardware para ahorrar contexto de interrupción. Es por eso que el código del kernel no puede tener una zona roja.

  • r11: syscall/sysret utilícelo para guardar / restaurar RFLAGS del espacio de usuario. (Junto con RCX para guardar / restaurar el RIP del espacio de usuario).

Casos especiales de codificación en modo de direccionamiento:

(Consulte también ¿rbp no permitido como base SIB? Que trata solo sobre los modos de direccionamiento, donde copié esta parte de esta respuesta).

rbp/r13 no puede ser un registro base sin desplazamiento: esa codificación en su lugar significa: (en ModRM) rel32 (Relativo a RIP) o (en SIB) disp32 sin registro base. (r13 utiliza los mismos 3 bits en ModRM / SIB, por lo que esta opción simplifica la decodificación al no hacer que el decodificador de longitud de instrucción mire el bit REX.B para obtener el cuarto bit de registro base). [r13] se ensambla a [r13 + disp8=0]. [r13+rdx] se ensambla a [rdx+r13] (evitando el problema intercambiando base / índice cuando esa es una opción).

rsp/r12 como registro base siempre necesita un byte SIB. (La codificación ModR / M de base = RSP es un código de escape para señalar un byte SIB, y nuevamente, más decodificadores tendrían que preocuparse por el REX prefix si r12 se manejó de manera diferente).

rsp no puede ser un registro de índice. Esto hace posible codificar [rsp], que es más útil que [rsp + rsp]. (Intel podría haber diseñado las codificaciones ModRM / SIB para los modos de direccionamiento de 32 bits (nuevo en 386) por lo que SIB-sin-índice solo era posible con base = ESP. Eso haría [eax + esp*4] posible y solo excluir [esp + esp*1/2/4/8]. Pero eso no es útil, por lo que simplificaron el hardware haciendo index = ESP el código para ningún índice, independientemente de la base. Esto permite dos formas redundantes de codificar cualquier modo de direccionamiento base o base + disp: con o sin un SIB.)

r12pueden ser un registro de índice. A diferencia de los otros casos, esto no afecta la decodificación de la longitud de la instrucción. Además, no se puede solucionar con una codificación más larga como en los otros casos. AMD quería que el registro de AMD64 fuera lo más ortogonal posible, por lo que tiene sentido que gasten algunos transistores adicionales para verificar REX.X como parte de la decodificación de índice / sin índice. Por ejemplo, [rsp + r12*4] requiere index = r12, por lo que teniendo r12 un propósito no completamente generalizado haría de AMD64 un peor objetivo del compilador.

   0:   41 8b 03                mov    eax,DWORD PTR [r11]
   3:   41 8b 04 24             mov    eax,DWORD PTR [r12]      # needs a SIB like RSP
   7:   41 8b 45 00             mov    eax,DWORD PTR [r13+0x0]  # needs a disp8 like RBP
   b:   41 8b 06                mov    eax,DWORD PTR [r14]
   e:   41 8b 07                mov    eax,DWORD PTR [r15]
  11:   43 8b 04 e3             mov    eax,DWORD PTR [r11+r12*8] # *can* be an index

A los compiladores les gusta cuando todos los registros pueden se puede utilizar para cualquier cosa, restringiendo únicamente la asignación de registros para unas pocas operaciones de casos especiales. Esto es lo que se entiende por ortogonalidad de registro.

Recuerda que puedes recomendar este artículo si te fue de ayuda.

¡Haz clic para puntuar esta entrada!
(Votos: 0 Promedio: 0)



Utiliza Nuestro Buscador

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *