¿Cómo se evita la omisión de instrucciones durante las llamadas a procedimientos en arquitecturas segmentadas?

Tengo una pregunta sobre el registro de una PC (IP en la jerga x86). En la mayoría de las arquitecturas, se actualiza durante una etapa de ejecución y, por lo tanto, almacena una dirección de la siguiente instrucción que se recuperará. Me pareció claro hasta que comencé a razonar sobre una arquitectura segmentada. Por ejemplo, imagine la siguiente tubería RISC clásica con 5 etapas (Obtener, Decodificar, Ejecutar, Acceso a memoria, Reescribir) que se completa con las siguientes instrucciones (designadas como "a", "b", "c", "d" ):

F D E M W
c b a - -

0x12 call [0x100] ; a
0x14 mov ax, 10   ; b
0x16 add ax, 2    ; c
0x18 nop          ; d <- IP

En el momento en que una instrucción "a" llega a la etapa E (Ejecutar) en la canalización, ya está llena con instrucciones posteriores y puntos de IP a una instrucción en la dirección 0x18 (la siguiente a buscar). Cuando se ejecuta "call [0x100]", guarda el contenido de la IP (una dirección de retorno) en la pila. ¡Pero obviamente no es la dirección de la instrucción que sigue a "call [0x100]"! Entonces, cuando regresamos de la LLAMADA, efectivamente saltamos sobre 2 instrucciones ya que la tubería se vacía durante la ejecución de la LLAMADA.

Lo que significa que:

  • Hay otro registro oculto que almacena la dirección de las instrucciones que se ejecutan y se almacena en la pila en lugar de IP
  • no funciona de esta manera
  • me falta algo :)
Para parte de la respuesta, busque "MIPS branch delay slot". (generalmente considerado por los diseñadores posteriores como una muy mala idea...)

Respuestas (1)

Leyendo el manual de referencia sobre el ensamblaje X86... https://courses.cs.washington.edu/courses/cse548/05wi/files/x86-reference-long.pdf

encontrará esto... "La instrucción de llamada llama a procedimientos cercanos utilizando un puntero completo. La llamada hace que se ejecute el procedimiento nombrado en el operando. Cuando se completa el procedimiento llamado, el flujo de ejecución se reanuda en la instrucción que sigue a la instrucción de llamada (consulte la instrucción de retorno)"

Esto le indica que la llamada debe devolverse primero. Esto significa que cuando se ejecute, las instrucciones noop se colocarán después hasta que regrese.

Inmediatamente después de que se llame, el bloque de control determinará que se trata de una llamada, que es un comando que requiere la acción de la unidad de detección de peligros. Esta unidad dispara lo que es esencialmente una interrupción de llamada. Entonces, inmediatamente después de ver esta instrucción, se produce esta interrupción para llamar a noops y no permitirá que la PC aumente.

Lo que describiste es claro para mí, pero mi pregunta es diferente. A menos que se trate de una canalización, el mecanismo de guardar/restaurar el contador del programa es sencillo: cuando se ejecuta CALL, la dirección de la siguiente instrucción se inserta en la pila de la pila y se coloca en el contador del programa al regresar. Sin embargo, tengo curiosidad por saber cómo se hace lo mismo en las arquitecturas canalizadas, ya que el contador del programa señalará varias instrucciones antes de que CALL llegue a la etapa de ejecución. Cómo se aborda esto es lo que me interesa.
@raiks Te tengo. Lo estás pensando demasiado :) Lo editaré.
Entonces, básicamente está diciendo que una detección temprana de CALL hace que una CPU inserte burbujas en la canalización evitando que se incremente un contador de programa hasta que se ejecute CALL, ¿verdad? Este esquema sería viable en mi ejemplo solo si la detección se realizara en la primera etapa (Fetch). Entonces, un contador de programa aún apuntaría a la instrucción que sigue a CALL y retendría su valor hasta el punto en que CALL alcanzó la etapa de Ejecución. Si te entendí bien, es interesante cómo se logra la detección temprana antes de la etapa de decodificación, especialmente en canalizaciones muy largas donde cada etapa es muy especializada.
@raiks Exactamente. Esta detección temprana es crucial una vez que entramos en un mundo de periféricos y miramos más allá del procesador. El procesador debe tener la capacidad de ser interrumpido en cualquier punto y puesto en noops. La llamada tiene un lugar en la tabla de ISR que le permite seguir este esquema de detección temprana. MIPS hace lo mismo con syscall.
@raiks Para comentar sobre los procesadores especializados muy largos, esto se divide de manera muy similar a un sistema más grande. Usted escoge y elige lo que necesita interrumpir dónde y en algunas arquitecturas puede encontrar señales de inicio que deben engancharse al siguiente registro pero que se vacían por el mismo principio.
Se pone realmente interesante cuando se trata de interrupciones. Si bien la técnica de detección temprana debería funcionar para interrupciones de software (INT xx), que son esencialmente un caso especial de llamadas a subrutinas, no funcionará para interrupciones de hardware. Cuando se dispara una interrupción de hardware, la canalización de la CPU ya está llena de instrucciones y no tiene opción para detectarla temprano e insertar burbujas que impidan el incremento del contador del programa como discutimos antes.
La única solución que puedo imaginar es calcular el tamaño de las instrucciones en la tubería y el contador del programa "rebobinado" (restando este tamaño del valor del contador del programa actual). Luego puede ser empujado a la pila y restaurado por un ISR más tarde. Sin embargo... La forma más fácil de manejar todos los casos que discutimos sería simplemente tener otro registro para almacenar una dirección de una instrucción actualmente ejecutada. Hasta donde yo sé, esto se hace en MIPS, por ejemplo, pero hablamos de x86 para el que las cosas parecen ser diferentes.
@raiks Para interrupciones externas, el estado actual de todos los registros se guarda. Los almacena en el banco de registros adecuado para que la pila, el montón y los registros simples puedan usarse donde quiera que haya ido. Cuando regresemos, el estado actual y toda la información estarán a salvo.