En informática , ingeniería informática e implementaciones de lenguajes de programación , una máquina de pila es un procesador o una máquina virtual de procesos cuya interacción principal consiste en mover valores temporales de corta duración hacia y desde una pila de almacenamiento . En el caso de un procesador de hardware, se utiliza una pila de hardware . El uso de una pila reduce significativamente el número de registros del procesador necesarios . Las máquinas de pila extienden los autómatas de pila con operaciones adicionales de carga/almacenamiento o múltiples pilas y, por lo tanto, son Turing-completas .
Diseño
La mayoría o todas las instrucciones de máquina de pila asumen que los operandos provendrán de la pila y que los resultados se colocarán en ella. La pila puede contener fácilmente más de dos entradas o más de un resultado, por lo que se puede calcular un amplio conjunto de operaciones. En el código máquina de pila (a veces llamado código p ), las instrucciones frecuentemente solo tienen un código de operación que ordena una operación, sin campos adicionales que identifiquen una constante, registro o celda de memoria, lo que se conoce como formato de dirección cero . [ 1 ] Se dice que una computadora que opera de tal manera que la mayoría de sus instrucciones no incluyen direcciones explícitas utiliza instrucciones de dirección cero. [ 2 ] Esto simplifica enormemente la decodificación de instrucciones. Las bifurcaciones, las instrucciones de carga inmediata y las instrucciones de carga/almacenamiento requieren un campo de argumento, pero las máquinas de pila a menudo organizan los casos frecuentes de estos aún se ajustan junto con el código de operación en un grupo compacto de bits . La selección de operandos a partir de resultados anteriores se realiza implícitamente mediante el ordenamiento de las instrucciones. Algunos conjuntos de instrucciones de máquina de pila están destinados a la ejecución interpretativa de una máquina virtual, en lugar de controlar directamente el hardware.
Los operandos constantes enteros se insertan mediante instrucciones PushOR . A menudo se accede a la memoria mediante instrucciones OR independientes que contienen una dirección de memoria o que calculan la dirección a partir de los valores de la pila. Todas las máquinas de pila prácticas disponen de variantes de los códigos de operación de carga y almacenamiento para acceder a variables locales y parámetros formales sin cálculos explícitos de direcciones. Esto puede hacerse mediante desplazamientos desde la dirección actual de la cima de la pila o desde un registro base de marco estable.Load ImmediateLoadStore
El conjunto de instrucciones ejecuta la mayoría de las acciones de la ALU mediante operaciones en notación posfija ( notación polaca inversa ) que actúan únicamente sobre la pila de expresiones, no sobre los registros de datos ni las celdas de la memoria principal. Esto resulta muy conveniente para la ejecución de lenguajes de alto nivel, ya que la mayoría de las expresiones aritméticas se pueden traducir fácilmente a notación posfija.

Por ejemplo, consideremos la expresión A *( B − C )+( D + E ), escrita en notación polaca inversa como A B C − * D E + +. Compilar y ejecutar esto en una máquina de pila imaginaria simple tomaría la forma:
# Contenido de la pila (de izquierda a derecha = superior = más reciente): pulsa A # A empujar B # BA empujar C # CBA restar # B−CA multiplicar # A*(B−C) empujar D # DA*(B−C) empujar E # EDA*(B−C) sumar # D+EA*(B−C) sumar # A*(B−C)+(D+E)
Las operaciones aritméticas de resta, multiplicación y suma actúan sobre los dos operandos superiores de la pila. El ordenador toma ambos operandos de los valores más recientes (superiores) de la pila y los reemplaza con la diferencia, suma o producto calculados. En otras palabras, los operandos de la instrucción se extraen de la pila y su resultado se vuelve a insertar, listo para la siguiente instrucción.
Las máquinas de pila pueden tener su pila de expresiones y su pila de llamadas y retorno separadas o como una estructura integrada. Si están separadas, las instrucciones de la máquina de pila se pueden procesar en paralelo con menos interacciones y menor complejidad de diseño, por lo que generalmente se ejecutará más rápido.
La optimización del código de pila compilado es perfectamente posible. Se ha demostrado que la optimización del back-end de la salida del compilador mejora significativamente el código, [ 3 ] [ 4 ] y potencialmente el rendimiento, mientras que la optimización global dentro del propio compilador logra mayores beneficios. [ 5 ]
Almacenamiento apilado
Algunas máquinas de pila tienen una pila de tamaño limitado, implementada como un archivo de registros. La ALU accede a ella mediante un índice. Un archivo de registros grande utiliza muchos transistores, por lo que este método solo es adecuado para sistemas pequeños. Algunas máquinas tienen tanto una pila de expresiones en memoria como una pila de registros independiente. En este caso, el software o una interrupción pueden transferir datos entre ellas. Otras máquinas tienen una pila de tamaño ilimitado, implementada como una matriz en la RAM, que se almacena en caché mediante varios registros de direcciones de "parte superior de la pila" para reducir el acceso a la memoria. Excepto para las instrucciones explícitas de "carga desde memoria", el orden de uso de los operandos es idéntico al orden de los operandos en la pila de datos, por lo que se puede lograr una excelente precarga fácilmente.
Consideremos X+1. Se compila a Load X; Load 1; Add. Con una pila almacenada completamente en RAM, esto realiza escrituras y lecturas implícitas de la pila en memoria:
- Cargar X, guardar en la memoria
- Cargar 1, guardar en memoria
- Extrae 2 valores de la memoria, súmalos y guarda el resultado en la memoria.
para un total de 5 referencias a la caché de datos.
El siguiente paso es una máquina de pila o intérprete con un único registro en la parte superior de la pila. El código anterior entonces hace lo siguiente:
- Cargar X en el registro TOS vacío (si es una máquina de hardware) o enviar el registro TOS a la memoria, cargar X en el registro TOS (si es un intérprete).
- Escribir el registro TOS en la memoria, cargar 1 en el registro TOS.
- Extraiga el operando izquierdo de la memoria, agréguelo al registro TOS y déjelo allí.
En el peor de los casos, se requieren un total de 3 referencias a la caché de datos. Generalmente, los intérpretes no controlan si la pila está vacía, ya que no es necesario: cualquier valor por debajo del puntero de pila es un valor no vacío, y el registro de caché TOS siempre se mantiene activo. Sin embargo, los intérpretes Java típicos no almacenan en búfer la parte superior de la pila de esta manera, dado que el programa y la pila contienen una combinación de valores de datos cortos y largos.
Si la máquina de pila cableada tiene 2 o más registros de pila superior, o un archivo de registros, entonces en este ejemplo se evita todo acceso a la memoria y solo hay 1 ciclo de caché de datos.
Historia e implementaciones
La descripción de un método que requería almacenar solo dos valores a la vez en registros, con un conjunto limitado de operandos predefinidos que podían ampliarse mediante la definición de operandos, funciones y subrutinas adicionales, fue presentada por primera vez en una conferencia por Robert S. Barton en 1961. [ 6 ] [ 7 ]
Máquinas apiladoras comerciales
Ejemplos de conjuntos de instrucciones de pila ejecutados directamente en hardware incluyen:
- La computadora Z4 (1945) de Konrad Zuse tenía una pila de 2 niveles. [ 8 ] [ 9 ]
- La arquitectura de grandes sistemas de Burroughs (desde 1961)
- La máquina English Electric KDF9 . Entregada por primera vez en 1964, la KDF9 tenía una pila de registros aritméticos de 19 niveles de profundidad y una pila de 17 niveles de profundidad para las direcciones de retorno de las subrutinas.
- la minicomputadora Collins Radio Collins Adaptive Processing System (CAPS, desde 1969) y el microprocesador Rockwell Collins Advanced Architecture (AAMP, desde 1981). [ 10 ]
- La Xerox Dandelion , presentada el 27 de abril de 1981, y la Xerox Daybreak utilizaban una arquitectura de máquina apilada para ahorrar memoria. [ 11 ] [ 12 ]
- La máquina virtual Pascal p de la UCSD (al igual que el Pascal MicroEngine y muchos otros) ofrecía un entorno de programación completo para estudiantes en los primeros microprocesadores de 8 bits con conjuntos de instrucciones limitados y poca RAM, mediante la compilación a una máquina de pila virtual.
- MU5 e ICL Serie 2900. Máquinas híbridas de pila y acumulador. El registro acumulador almacenaba en búfer el valor de datos superior de la pila de memoria. Variantes de los códigos de operación de carga y almacenamiento controlaban cuándo se volcaba ese registro a la pila de memoria o se recargaba desde allí.
- HP 3000 (clásico, no PA-RISC)
- Sistemas HP 9000 basados en el microprocesador HP FOCUS . [ 13 ]
- Computadoras Tandem T/16. Similares a las HP 3000, excepto que los compiladores, no el microcódigo, controlaban cuándo la pila de registros se desbordaba a la pila de memoria o se rellenaba desde la pila de memoria.
- el microcontrolador Atmel MARC4 [ 14 ]
- Varios "chips Forth" [ 15 ] como el RTX2000, el RTX2010 , el F21 [ 16 ] y el PSC1000 [ 17 ] [ 18 ]
- La computadora ternaria Setun realizaba cálculos ternarios balanceados utilizando una pila.
- La máquina de pilas Ignite de Patriot Scientific , diseñada por Charles H. Moore, ostenta un récord de densidad funcional .
- Microprocesador resistente a la radiación Saab Ericsson Space Thor [ 19 ]
- Transputadores Inmos .
- ZPU Una CPU físicamente pequeña diseñada para supervisar sistemas FPGA . [ 20 ]
- Algunas calculadoras técnicas de mano utilizan notación polaca inversa en su teclado, en lugar de teclas de paréntesis. Se trata de una forma de máquina de pila. La tecla de suma depende de que sus dos operandos ya se encuentren en las posiciones superiores correctas de la pila visible para el usuario.
Máquinas de pila virtual
Ejemplos de máquinas de pila virtual interpretadas en software:
- el código interpretativo Whetstone ALGOL 60 , [ 21 ] en el que se basaron algunas características del Burroughs B6500
- la máquina p de Pascal de la UCSD ; que se parecía mucho a la de Burroughs.
- La máquina de código P de Niklaus Wirth
- Charla informal
- el conjunto de instrucciones de la máquina virtual Java (tenga en cuenta que solo el conjunto de instrucciones abstracto se basa en la pila; HotSpot, la máquina virtual Java de Sun, por ejemplo, no implementa el intérprete real en software, sino como fragmentos de código ensamblador escritos a mano).
- el código de bytes de WebAssembly
- El Sistema de Ejecución Virtual (VES) para el conjunto de instrucciones del Lenguaje Intermedio Común (CIL) del .NET Framework (ECMA 335)
- el lenguaje de programación Forth , especialmente la máquina virtual integral
- PostScript de Adobe
- Lenguaje de programación SwapDrop de Sun Microsystems para la identificación de tarjetas inteligentes Sun Ray.
- Máquina virtual ActionScript 2 (AVM2) de Adobe
- EVM de Ethereum
- el intérprete de código de bytes de CPython
- el intérprete de código de bytes Ruby YARV
- la máquina virtual Rubinius
- La calculadora bs en Unix utiliza una máquina de pila virtual para procesar comandos, después de transponer primero el formato del lenguaje de entrada proporcionado a la notación polaca inversa.
- La calculadora dc , uno de los programas Unix más antiguos , utiliza la notación polaca inversa.
- El intérprete del lenguaje de programación Lua se basaba en pila hasta la versión 5.0. [ 22 ] La API de C sigue siendo así a partir de la versión 5.x.
- La máquina virtual TON (TVM) para los contratos inteligentes de The Open Network
- El sistema de fuentes TrueType de Apple tiene un conjunto de instrucciones basado principalmente en pilas, con una sección de almacenamiento separada indexada por número. [ 23 ]
Máquinas híbridas
Las máquinas de pila puras son bastante ineficientes para procedimientos que acceden a múltiples campos del mismo objeto. El código de la máquina de pila debe recargar el puntero del objeto para cada cálculo de puntero + desplazamiento. Una solución común consiste en añadir algunas características de la máquina de registros a la máquina de pila: un archivo de registros visible dedicado a almacenar direcciones e instrucciones de estilo de registro para realizar cargas y cálculos de direcciones sencillos. No es común que los registros sean de propósito general, ya que entonces no hay una razón de peso para tener una pila de expresiones e instrucciones postfijas.
Otro híbrido común consiste en partir de una arquitectura de máquina de registros y añadir otro modo de dirección de memoria que emula las operaciones push o pop de las máquinas de pila: "memaddress = reg; reg += instr.displ". Esto se utilizó por primera vez en el miniordenador PDP-11 de DEC . [ 24 ] Esta característica se mantuvo en los ordenadores VAX y en los microprocesadores Motorola de las series 6809 y 68000. Esto permitió el uso de métodos de pila más sencillos en los primeros compiladores. También soportaba eficientemente máquinas virtuales que utilizaban intérpretes de pila o código multihilo . Sin embargo, esta característica no ayudó a que el propio código de la máquina de registros se volviera tan compacto como el código de máquina de pila puro. Además, la velocidad de ejecución era menor que cuando se compilaba correctamente a la arquitectura de registros. Es más rápido cambiar el puntero de la parte superior de la pila solo ocasionalmente (una vez por llamada o retorno) que subirlo y bajarlo constantemente a lo largo de cada instrucción del programa, e incluso es más rápido evitar por completo las referencias a memoria.
Más recientemente, las denominadas máquinas de pila de segunda generación han adoptado un conjunto dedicado de registros para funcionar como registros de direcciones, descargando la tarea de direccionamiento de memoria de la pila de datos. Por ejemplo, MuP21 se basa en un registro llamado "A", mientras que los procesadores GreenArrays más recientes se basan en dos registros: A y B. [ 25 ]
La familia de microprocesadores Intel x86 utiliza un conjunto de instrucciones de registro (acumulador) para la mayoría de las operaciones, pero emplea instrucciones de pila para la aritmética de punto flotante del Intel 8087 , que se remonta al coprocesador iAPX87 (8087) para los procesadores 8086 y 8088. Es decir, no dispone de registros de punto flotante accesibles por el programador, sino únicamente de una pila de 80 bits de ancho y 8 niveles de profundidad. El procesador x87 depende en gran medida de la CPU x86 para la ejecución de sus operaciones.
Computadoras que utilizan pilas de llamadas y marcos de pila
La mayoría de los ordenadores actuales (independientemente del conjunto de instrucciones) y la mayoría de los compiladores utilizan una gran pila de llamadas y retornos en memoria para organizar las variables locales de corta duración y los enlaces de retorno de todos los procedimientos o funciones activos. Cada llamada anidada crea un nuevo marco de pila en memoria, que persiste hasta que finaliza dicha llamada. Esta pila de llamadas y retornos puede ser gestionada completamente por el hardware mediante registros de direcciones especializados y modos de direccionamiento especiales en las instrucciones. O puede ser simplemente un conjunto de convenciones seguidas por los compiladores, utilizando registros genéricos y modos de direccionamiento de registro y desplazamiento. O puede ser una combinación de ambos.
Dado que esta técnica es prácticamente universal, incluso en máquinas de registro, no resulta útil denominar a todas estas máquinas como máquinas de pila. Este término se suele reservar para máquinas que también utilizan una pila de expresiones e instrucciones aritméticas de pila para evaluar las partes de una misma instrucción.
Los ordenadores suelen proporcionar acceso directo y eficiente a las variables globales del programa y a las variables locales únicamente del procedimiento o función más interna actual, el marco de pila superior. El direccionamiento de nivel superior del contenido de los marcos de pila de las funciones que realizan llamadas no suele ser necesario ni está soportado directamente por el hardware. Si fuera necesario, los compiladores lo soportan pasando punteros de marco como parámetros ocultos adicionales.
Algunas máquinas de pila Burroughs admiten referencias de nivel superior directamente en el hardware, con modos de direccionamiento especializados y un archivo de registro de "visualización" especial que almacena las direcciones de marco de todos los ámbitos externos. Actualmente, solo MCST Elbrus ha implementado esto en hardware. Cuando Niklaus Wirth desarrolló el primer compilador Pascal para la serie CDC 6000 , descubrió que era más rápido en general pasar los punteros de marco como una cadena, en lugar de actualizar constantemente matrices completas de punteros de marco. Este método de software tampoco añade sobrecarga para lenguajes comunes como C, que carecen de referencias de nivel superior.
Las mismas máquinas Burroughs también admitían el anidamiento de tareas o hilos. La tarea y su creador compartían los marcos de pila existentes en el momento de la creación de la tarea, pero no los marcos posteriores del creador ni los propios marcos de la tarea. Esto se implementaba mediante una pila tipo cactus , cuyo diagrama de disposición se asemejaba al tronco y los brazos de un cactus saguaro . Cada tarea tenía su propio segmento de memoria que contenía su pila y los marcos que le pertenecían. La base de esta pila estaba vinculada a la parte central de la pila de su creador. En máquinas con un espacio de direcciones plano convencional, la pila del creador y las pilas de tareas serían objetos de montón separados dentro de un mismo montón.
En algunos lenguajes de programación, los entornos de datos externos no siempre están anidados en el tiempo. Estos lenguajes organizan sus "registros de activación" de procedimientos como objetos de montón separados, en lugar de como marcos de pila añadidos a una pila lineal.
En lenguajes sencillos como Forth , que carecen de variables locales y nombres de parámetros, los marcos de pila solo contienen direcciones de retorno y la sobrecarga de gestión de marcos. Por lo tanto, su pila de retorno almacena direcciones de retorno sin formato, en lugar de marcos. La pila de retorno está separada de la pila de valores de datos para mejorar el flujo de configuración y retorno de llamadas.
Comparación con las máquinas de registro
Las máquinas de pila se comparan a menudo con las máquinas de registro, que almacenan valores en una matriz de registros . Las máquinas de registro pueden almacenar estructuras similares a pilas en esta matriz, pero poseen instrucciones que evitan la interfaz de pila. Las máquinas de registro superan habitualmente a las máquinas de pila, [ 26 ] y estas últimas siguen siendo un componente minoritario en los sistemas de hardware. Sin embargo, las máquinas de pila se utilizan con frecuencia en la implementación de máquinas virtuales debido a su simplicidad y facilidad de implementación. [ 27 ]
Instrucciones
Las máquinas de pila tienen una mayor densidad de código . A diferencia de las instrucciones comunes de las máquinas de pila, que caben fácilmente en 6 bits o menos, las máquinas de registro requieren dos o tres campos de número de registro por instrucción de la ALU para seleccionar los operandos; las máquinas de registro más densas tienen un promedio de aproximadamente 16 bits por instrucción, más los operandos. Las máquinas de registro también utilizan un campo de desplazamiento más amplio para los códigos de operación de carga y almacenamiento. El código compacto de una máquina de pila permite almacenar más instrucciones en la caché, lo que se traduce en una mayor eficiencia de la caché , reduciendo los costos de memoria o permitiendo sistemas de memoria más rápidos para un costo determinado. Además, la mayoría de las instrucciones de las máquinas de pila son muy simples, compuestas por un solo campo de código de operación o un solo campo de operando. Por lo tanto, las máquinas de pila requieren muy pocos recursos electrónicos para decodificar cada instrucción.
Un programa debe ejecutar más instrucciones cuando se compila para una máquina de pila que cuando se compila para una máquina de registros o una máquina de memoria a memoria. Cada carga de variable o constante requiere su propia instrucción de carga independiente, en lugar de estar incluida en la instrucción que utiliza ese valor. Si bien las instrucciones separadas pueden ser más simples y rápidas, el número total de instrucciones sigue siendo mayor.
La mayoría de los intérpretes de registros especifican sus registros por número. Pero los registros de una máquina anfitriona no se pueden acceder en una matriz indexada, por lo que se asigna una matriz de memoria para los registros virtuales. Por lo tanto, las instrucciones de un intérprete de registros deben usar memoria para pasar los datos generados a la siguiente instrucción. Esto hace que los intérpretes de registros sean mucho más lentos en microprocesadores fabricados con una regla de proceso fina (es decir, transistores más rápidos sin mejorar la velocidad del circuito, como el Haswell x86). Estos requieren varios ciclos de reloj para el acceso a la memoria, pero solo uno para el acceso a los registros. En el caso de una máquina de pila con un circuito de reenvío de datos en lugar de un archivo de registros, los intérpretes de pila pueden asignar los registros de la máquina anfitriona para los primeros operandos de la pila en lugar de la memoria de la máquina anfitriona.
En una máquina de pila, los operandos utilizados en las instrucciones siempre se encuentran en una posición fija (el fondo de la pila, que en un diseño de hardware podría estar siempre en la ubicación de memoria cero), lo que ahorra valioso espacio de almacenamiento en caché o en la CPU al evitar que se utilice para almacenar tantas direcciones de memoria o números de índice. Esto puede preservar dichos registros y caché para su uso en cálculos que no sean de flujo.
Valores temporales/locales
Algunos en la industria creen que las máquinas de pila ejecutan más ciclos de caché de datos para valores temporales y variables locales que las máquinas de registro. [ 28 ]
En las máquinas con pila, los valores temporales suelen volcarse a la memoria, mientras que en las máquinas con muchos registros, estos valores generalmente permanecen en los registros. (Sin embargo, estos valores a menudo deben volcarse a "marcos de activación" al final de la definición de un procedimiento, bloque básico o, como mínimo, a un búfer de memoria durante el procesamiento de interrupciones). Los valores volcados a la memoria aumentan el consumo de caché. Este efecto de volcado depende del número de registros ocultos utilizados para almacenar en búfer los valores de la parte superior de la pila, de la frecuencia de las llamadas a procedimientos anidados y de la velocidad de procesamiento de interrupciones del ordenador.
En las máquinas de registro que utilizan compiladores optimizadores, es muy común que las variables locales más utilizadas permanezcan en los registros en lugar de en las celdas de memoria del marco de pila. Esto elimina la mayoría de los ciclos de caché de datos necesarios para leer y escribir esos valores. El desarrollo de la "planificación de pila" para realizar análisis de variables vivas , y por lo tanto retener las variables clave en la pila durante períodos prolongados, ayuda a mitigar este problema. [ 3 ] [ 4 ] [ 5 ]
Por otro lado, las máquinas de registro deben volcar muchos de sus registros a la memoria a través de llamadas a procedimientos anidados. La decisión sobre qué registros volcar y cuándo se toma de forma estática en tiempo de compilación, en lugar de basarse en la profundidad dinámica de las llamadas. Esto puede generar un mayor tráfico de caché de datos que en una implementación avanzada de máquina de pila.
Subexpresiones comunes
En las máquinas de registro, una subexpresión común (una subexpresión que se usa varias veces con el mismo resultado) se puede evaluar solo una vez y su resultado se guarda en un registro rápido. Las reutilizaciones posteriores no tienen costo de tiempo ni de código, solo una referencia al registro. Esta optimización acelera tanto las expresiones simples (por ejemplo, cargar la variable X o el puntero P) como las expresiones complejas menos comunes.
En cambio, con las máquinas de pila, los resultados se pueden almacenar de dos maneras. Primero, se pueden almacenar usando una variable temporal en memoria. El almacenamiento y las recuperaciones posteriores cuestan instrucciones adicionales y ciclos de caché de datos adicionales. Hacer esto solo es ventajoso si el cálculo de la subexpresión cuesta más tiempo que la obtención de la memoria, lo cual en la mayoría de las CPU de pila, casi siempre es el caso. Nunca vale la pena para variables simples y accesos a punteros, porque estos ya tienen el mismo costo de un ciclo de caché de datos por acceso. Solo es marginalmente ventajoso para expresiones como X+1. Estas expresiones más simples constituyen la mayoría de las expresiones redundantes y optimizables en programas escritos en lenguajes que no son lenguajes concatenantes . Un compilador optimizador solo puede ganar en redundancias que el programador podría haber evitado en el código fuente.
El segundo método deja un valor calculado en la pila de datos, duplicándolo según sea necesario. Esto utiliza operaciones para copiar entradas de la pila. La pila debe tener una profundidad suficientemente baja para las instrucciones de copia disponibles de la CPU. El código de pila escrito manualmente suele utilizar este enfoque y alcanza velocidades similares a las de las máquinas de registro de propósito general. [ 29 ] [ 9 ] Desafortunadamente, los algoritmos para la "planificación de pila" óptima no son de uso generalizado en los lenguajes de programación.
Tuberías
En las máquinas modernas, el tiempo para obtener una variable de la caché de datos suele ser varias veces mayor que el necesario para las operaciones básicas de la ALU. Un programa se ejecuta más rápido y sin bloqueos si sus cargas de memoria pueden iniciarse varios ciclos antes de la instrucción que necesita esa variable. Las máquinas complejas pueden lograr esto con una profunda segmentación y una "ejecución fuera de orden" que examina y ejecuta muchas instrucciones a la vez. Las máquinas de registro incluso pueden lograrlo con un hardware "en orden" mucho más simple, una segmentación superficial y compiladores ligeramente más inteligentes. El paso de carga se convierte en una instrucción independiente, y esa instrucción se programa estáticamente mucho antes en la secuencia de código. El compilador coloca pasos independientes entre medias.
La planificación de accesos a memoria requiere registros explícitos y de reserva. Esto no es posible en máquinas de pila sin exponer algún aspecto de la microarquitectura al programador. Para la expresión AB −, B debe evaluarse y apilarse inmediatamente antes del paso Minus. Sin permutación de pila ni multihilo por hardware, se puede insertar relativamente poco código útil mientras se espera a que finalice Load B. Las máquinas de pila pueden sortear el retardo de memoria mediante una profunda canalización de ejecución fuera de orden que cubra muchas instrucciones a la vez, o, más probablemente, permutando la pila para poder trabajar en otras cargas de trabajo mientras se completa la carga, o intercalando la ejecución de diferentes hilos de programa, como en el sistema Unisys A9. [ 30 ] Sin embargo, las cargas computacionales cada vez más paralelas actuales sugieren que esto podría no ser la desventaja que se ha considerado en el pasado.
Las máquinas de pila pueden omitir la etapa de obtención de operandos de una máquina de registro. [ 29 ] Por ejemplo, en el microprocesador Java Optimized Processor (JOP), los dos operandos superiores de la pila entran directamente en un circuito de reenvío de datos que es más rápido que el archivo de registros. [ 31 ]
Ejecución fuera de orden
El algoritmo de Tomasulo encuentra paralelismo a nivel de instrucción emitiendo instrucciones a medida que sus datos están disponibles. Conceptualmente, las direcciones de las posiciones en una pila no difieren de los índices de registro de un archivo de registros. Esta perspectiva permite utilizar la ejecución fuera de orden del algoritmo de Tomasulo con máquinas de pila.
La ejecución fuera de orden en máquinas de pila parece reducir o evitar muchas dificultades teóricas y prácticas. [ 32 ] La investigación citada muestra que una máquina de pila de este tipo puede aprovechar el paralelismo a nivel de instrucción, y el hardware resultante debe almacenar en caché los datos de las instrucciones. Estas máquinas evitan eficazmente la mayoría de los accesos a la pila. El resultado alcanza un rendimiento (instrucciones por ciclo de reloj ) comparable al de las máquinas con arquitectura de carga-almacenamiento , con densidades de código mucho mayores (debido a que las direcciones de los operandos son implícitas).
Una cuestión que surgió en la investigación fue que se necesitan aproximadamente 1,88 instrucciones de una máquina de pila para realizar el trabajo de una sola instrucción en una máquina con arquitectura de carga y almacenamiento. Por lo tanto, las máquinas de pila competitivas con ejecución fuera de orden requieren aproximadamente el doble de recursos electrónicos para el seguimiento de las instrucciones ("estaciones de emisión"). Esto podría compensarse con ahorros en la caché de instrucciones, la memoria y los circuitos de decodificación de instrucciones.
En su interior oculta una caja registradora más rápida.
Algunas máquinas de pila sencillas tienen un diseño de chip totalmente personalizado hasta el nivel de los registros individuales. El registro de dirección de la parte superior de la pila y los N búferes de datos de la parte superior de la pila están construidos a partir de circuitos de registro individuales separados, con sumadores separados y conexiones ad hoc.
Sin embargo, la mayoría de las máquinas de pila se construyen a partir de componentes de circuitos más grandes, donde los N búferes de datos se almacenan juntos en un banco de registros y comparten buses de lectura/escritura. Las instrucciones de pila decodificadas se asignan a una o más acciones secuenciales en ese banco de registros oculto. Las cargas y las operaciones de la ALU actúan sobre algunos de los registros superiores, y los desbordamientos y llenados implícitos actúan sobre los registros inferiores. El decodificador permite que el flujo de instrucciones sea compacto. Pero si el flujo de código tuviera campos de selección de registro explícitos que manipularan directamente el banco de registros subyacente, el compilador podría aprovechar mejor todos los registros y el programa se ejecutaría más rápido.
Las máquinas de pila microprogramadas son un ejemplo de esto. El motor de microcódigo interno es una especie de máquina de registros tipo RISC o una máquina tipo VLIW que utiliza múltiples bancos de registros. Cuando se controla directamente mediante microcódigo específico para la tarea, ese motor realiza mucho más trabajo por ciclo que cuando se controla indirectamente mediante código de pila equivalente para esa misma tarea.
Los traductores de código objeto que tradujeron el código para las máquinas de pila HP 3000 y Tandem NonStop al código para los reemplazos RISC basados en registros de esas máquinas son otro ejemplo. [ 33 ] [ 34 ] Tradujeron secuencias de código de pila a secuencias equivalentes de código RISC. Las optimizaciones "locales" menores eliminaron gran parte de la sobrecarga de una arquitectura de pila. Se utilizaron registros de reserva para factorizar los cálculos de direcciones repetidos. El código traducido aún conservaba una gran cantidad de sobrecarga de emulación debido a la incompatibilidad entre las máquinas originales y de destino. A pesar de esa carga, la eficiencia de ciclo del código traducido coincidió con la eficiencia de ciclo del código de pila original. Y cuando el código fuente se recompiló directamente a la máquina de registros mediante compiladores optimizadores, la eficiencia se duplicó. Esto demuestra que la arquitectura de pila y sus compiladores no optimizadores estaban desperdiciando más de la mitad de la potencia del hardware subyacente.
Los registros son herramientas útiles para la computación debido a su alto ancho de banda y baja latencia, en comparación con las referencias a memoria a través de cachés de datos. En una máquina simple, el registro permite leer dos registros independientes y escribir en un tercero, todo en un ciclo de la ALU con una latencia de un ciclo o menos. En cambio, la caché de datos correspondiente solo puede iniciar una lectura o una escritura (no ambas) por ciclo, y la lectura suele tener una latencia de dos ciclos de la ALU. Esto representa un tercio del rendimiento con el doble de retardo de la tubería. En una máquina compleja como Athlon, que completa dos o más instrucciones por ciclo, el registro permite leer cuatro o más registros independientes y escribir en otros dos, todo en un ciclo de la ALU con una latencia de un ciclo. En cambio, la caché de datos de doble puerto correspondiente solo puede iniciar dos lecturas o escrituras por ciclo, con una latencia de varios ciclos. Nuevamente, esto representa un tercio del rendimiento de los registros. Construir una caché con puertos adicionales es muy costoso.
Dado que la pila es un componente de la mayoría de los programas de software, incluso cuando el software utilizado no es estrictamente una máquina de pila, una máquina de pila de hardware podría imitar con mayor precisión el funcionamiento interno de sus programas. Los registros del procesador tienen un alto costo térmico, y una máquina de pila podría ofrecer una mayor eficiencia energética. [ 35 ]
Interrumpe
Responder a una interrupción implica guardar los registros en una pila y luego saltar al código del controlador de interrupción. A menudo, las máquinas de pila responden más rápidamente a las interrupciones, ya que la mayoría de los parámetros ya están en la pila y no es necesario agregarlos allí. Algunas máquinas de registro solucionan esto mediante el uso de múltiples archivos de registro que se pueden intercambiar instantáneamente [ 36 ] , pero esto aumenta los costos y ralentiza el archivo de registro.
Intérpretes
Los intérpretes para máquinas de pila virtual son más fáciles de construir que los intérpretes para máquinas de registro; la lógica para manejar los modos de direcciones de memoria se encuentra en un solo lugar en lugar de repetirse en muchas instrucciones. Las máquinas de pila también tienden a tener menos variaciones de un código de operación; un código de operación generalizado maneja tanto los casos frecuentes como los casos límite menos comunes de referencias a memoria o configuración de llamadas a funciones. (Sin embargo, la densidad del código a menudo mejora al agregar formas cortas y largas para la misma operación).
Los intérpretes para máquinas de pila virtual suelen ser más lentos que los intérpretes para otros estilos de máquinas virtuales. [ 37 ] Esta ralentización es peor cuando se ejecutan en máquinas anfitrionas con profundas canalizaciones de ejecución, como los chips x86 actuales.
En algunos intérpretes, el intérprete debe ejecutar un salto de conmutación de N vías para decodificar el siguiente código de operación y ramificarse a sus pasos para ese código de operación en particular. Otro método para seleccionar códigos de operación es el código encadenado . Los mecanismos de precarga de la máquina anfitriona no pueden predecir ni obtener el destino de ese salto indexado o indirecto. Por lo tanto, la canalización de ejecución de la máquina anfitriona debe reiniciarse cada vez que el intérprete alojado decodifica otra instrucción virtual. Esto ocurre con más frecuencia en las máquinas de pila virtual que en otros estilos de máquinas virtuales. [ 38 ]
Un ejemplo es el lenguaje de programación Java . Su máquina virtual canónica se especifica como una máquina de pila de 8 bits. Sin embargo, la máquina virtual Dalvik para Java utilizada en los teléfonos inteligentes Android es una máquina de registro virtual de 16 bits, una elección realizada por razones de eficiencia. Las instrucciones aritméticas obtienen o almacenan directamente variables locales a través de campos de instrucción de 4 bits (o mayores). [ 39 ] De manera similar, la versión 5.0 de Lua reemplazó su máquina de pila virtual con una máquina de registro virtual más rápida. [ 40 ] [ 41 ]
Desde que la máquina virtual Java se popularizó, los microprocesadores han empleado predictores de bifurcación avanzados para saltos indirectos. [ 42 ] Este avance evita la mayoría de los reinicios de la tubería de saltos de N vías y elimina gran parte de los costos de conteo de instrucciones que afectan a los intérpretes de pila.
Véase también
Referencias
- ↑ Beard, Bob (otoño de 1997). "La computadora KDF9: 30 años después" . Computer RESURRECTION .
- ↑ Hayes, John P. (1978). Arquitectura y organización de computadoras . McGraw-Hill International Book Company. pág. 164. ISBN 0-07-027363-4.
- 1 2 Koopman, Jr., Philip John (1994). "Una exploración preliminar de la generación optimizada de código de pila" (PDF) . Revista de aplicaciones e investigación de Forth . 6 (3).
- 1 2 Bailey, Chris (2000). "Programación entre límites de operandos de pila: un estudio preliminar" (PDF) . Actas de la Conferencia Euroforth 2000 .
- 1 2 Shannon, Mark; Bailey, Chris (2006). "Asignación global de pila: asignación de registros para máquinas de pila" (PDF) . Actas de la Conferencia Euroforth 2006 .
- ↑ Barton, Robert S. (9 de mayo de 1961). «Un nuevo enfoque para el diseño funcional de una computadora digital» . Ponencias presentadas en la Conferencia Conjunta Occidental de Computación IRE-AIEE-ACM del 9 al 11 de mayo de 1961. Conferencia Conjunta Occidental de Computación IRE-AIEE-ACM de 1961. págs. 393-396 . doi : 10.1145/1460690.1460736 . ISBN 978-1-45037872-7. S2CID 29044652 .
{{cite conference}}: Incompatibilidad de ISBN/Fecha ( ayuda ) - ↑ Barton, Robert S. (1987). "Un nuevo enfoque para el diseño funcional de una computadora digital" . IEEE Annals of the History of Computing . 9 (1): 11– 15. Bibcode : 1987IAHC....9a..11B . doi : 10.1109/MAHC.1987.10002 .
- ↑ Blaauw, Gerrit Anne ; Brooks, Jr., Frederick Phillips (1997). Arquitectura de computadoras: conceptos y evolución . Boston, Massachusetts, EE. UU.: Addison-Wesley Longman Publishing Co., Inc.
- 1 2 LaForest, Charles Eric (abril de 2007). "2.1 Lukasiewicz y la primera generación: 2.1.2 Alemania: Konrad Zuse (1910–1995); 2.2 La primera generación de computadoras de pila: 2.2.1 Zuse Z4". Arquitectura de computadoras de pila de segunda generación (PDF) (tesis). Waterloo, Canadá: Universidad de Waterloo . pág. 8, 11, etc. Archivado (PDF) del original el 20 de enero de 2022. Recuperado el 2 de julio de 2022 . (178 páginas)
- ↑ Greve, David A.; Wilding, Matthew M. (1998-01-12). "El primer procesador Java del mundo" . Electronic Engineering Times .
- ↑ "Principios de funcionamiento del procesador Mesa" . Museo de Informática DigiBarn . Xerox. Archivado del original el 14 de mayo de 2024. Consultado el 20 de septiembre de 2023 .
- ↑ "DigiBarn: La Xerox Star 8010 "Dandelion"" . Museo de Computación DigiBarn. Archivado del original el 3 de mayo de 2024. Consultado el 20 de septiembre de 2023 .
- ↑ "Conjunto de instrucciones para un procesador de 32 bits de un solo chip" . Hewlett-Packard Journal . 34 (8). Hewlett-Packard. Agosto de 1983. Consultado el 5 de febrero de 2024 .
- ↑ Guía del programador de microcontroladores MARC4 de 4 bits (PDF) . Atmel .
- ↑ "Forth chips" . Colorforth.com . Archivado del original el 15 de febrero de 2006. Consultado el 8 de octubre de 2017 .
- ↑ "Descripción general del microprocesador F21" . Ultratechnology.com . Consultado el 8 de octubre de 2017 .
- ↑ "ForthFreak wiki" . GitHub.com . 2017-08-25 . Consultado el 2017-10-08 .
- ↑ "¡Un chip Java disponible ahora!" . Developer.com . 8 de abril de 1999. Archivado del original el 30 de septiembre de 2022. Consultado el 7 de julio de 2022 .
- ↑ "Adaptación del compilador GNU C al microprocesador Thor" (PDF) . 4 de diciembre de 1995. Archivado del original (PDF) el 20 de agosto de 2011. Consultado el 30 de marzo de 2011 .
- ↑ "ZPU: la CPU de 32 bits más pequeña del mundo con una cadena de herramientas GCC: Descripción general" . opencores.org . Consultado el 7 de febrero de 2015 .
- ↑ Randell, Brian ; Russell, Lawford John (1964). Implementación de Algol 60 (PDF) . Londres, Reino Unido: Academic Press . ISBN 0-12-578150-4.
{{cite book}}: Incompatibilidad de ISBN/Fecha ( ayuda ) - ↑ "La implementación de Lua 5.0" (PDF) .
- ↑ "Conjunto de instrucciones" . Manual de referencia de TrueType .
- ↑ Duncan, Fraser George (1977-05-01). "Desarrollo de Stack Machine: Australia, Gran Bretaña y Europa" (PDF) . Computer . Vol. 10, n.º 5. Universidad de Bristol, Bristol, Virginia, EE. UU. pp. 50–52 . doi : 10.1109/MC.1977.315873 . eISSN 1558-0814 . ISSN 0018-9162 . S2CID 17013010. CODEN CPTRB4 . Archivado del original (PDF) el 15-10-2023 . Recuperado el 15-10-2023 . (3 páginas)
- ↑ "Instrucciones de colorForth" . Colorforth.com . Archivado del original el 10 de marzo de 2016. Consultado el 8 de octubre de 2017 .(Conjunto de instrucciones de los núcleos del F18A, denominado colorForth por razones históricas).
- ↑ Shi, Yunhe; Gregg, David; Beatty, Andrew; Ertl, M. Anton (2005). "Duelo de máquinas virtuales: pila contra registros". Actas de la 1.ª conferencia internacional ACM/USENIX sobre entornos de ejecución virtual . págs. 153–163 . doi : 10.1145/1064979.1065001 . ISBN 1595930477. S2CID 811512 .
- ↑ Hyde, Randall (2004). Escribe código excelente, vol. 2: Pensando a bajo nivel, escribiendo a alto nivel . Vol. 2. No Starch Press . pág. 391. ISBN 978-1-59327-065-0. Consultado el 30 de junio de 2021 .
- ↑ John L. Hennessy ; David Andrew Patterson . Arquitectura de computadoras: un enfoque cuantitativo .Véase la discusión sobre las máquinas de pila.
- 1 2 Koopman, Jr., Philip John. "Stack Computers: the new wave" . Ece.cmu.edu . Consultado el 8 de octubre de 2017 .
- ↑ Introducción a los sistemas de la serie A (PDF) . Burroughs Corporation . Abril de 1986. Consultado el 20 de septiembre de 2023 .
- ↑ "Diseño e implementación de una máquina de pila eficiente" (PDF) . Jopdesign.com . Consultado el 8 de octubre de 2017 .
- ↑ Sinha, Steve; Chatterji, Satrajit; Ravindran, Kaushik. "BOOST: El sistema de pila fuera de orden de Berkeley" . Research Gate . Consultado el 11 de noviembre de 2023 .
- ↑ Bergh, Arndt; Keilman, Keith; Magenheimer, Daniel; Miller, James (diciembre de 1987). "Emulación de HP3000 en computadoras con arquitectura de precisión HP" (PDF) . Hewlett-Packard Journal . Hewlett-Packard : 87–89 . Archivado del original (PDF) el 22 de octubre de 2023. Consultado el 20 de septiembre de 2023 .
- ↑ Kristy Andrews; Duane Sand (octubre de 1992). "Migración de una familia de computadoras CISC a RISC mediante traducción de código objeto". Actas de ASPLOS-V .
- ↑ "Documentos" . GreenArrays, Inc. Tecnología F18A . Consultado el 7 de julio de 2022 .
- ↑ Manual de la CPU 8051, Intel, 1980
- ↑ Shi, Yunhe; Gregg, David; Beatty, Andrew; Ertle, M. Anton. "Virtual Machine Showdown: Stack vs. Register Machine" (PDF) . Usenix.org . Consultado el 8 de octubre de 2017 .
- ↑ Davis, Brian; Beatty, Andrew; Casey, Kevin; Gregg, David; Waldron, John. "Argumentos a favor de las máquinas de registro virtual" (PDF) . Scss.tcd.ie. Consultado el 20 de septiembre de 2023 .
- ↑ Bornstein, Dan (29 de mayo de 2008). "Presentación de los aspectos internos de la máquina virtual Dalvik" (PDF) . pág. 22. Archivado del original (PDF) el 5 de septiembre de 2008. Consultado el 16 de agosto de 2010 .
- ↑ "La implementación de Lua 5.0" (PDF) . Lua.org . Consultado el 8 de octubre de 2017 .
- ↑ "La máquina virtual de Lua 5.0" (PDF) . Inf.puc-rio.br . Consultado el 8 de octubre de 2017 .
- ↑ "Predicción de ramificaciones y el desempeño de los intérpretes: no confíes en el folclore" . Hal.inria.fr . Consultado el 20 de septiembre de 2023 .
Enlaces externos
- CPU casera en una FPGA : máquina de pila casera que utiliza FPGA
- Computadora Mark 1 FORTH : una máquina de pila casera que utiliza circuitos lógicos discretos.
- Computadora Mark 2 FORTH : máquina de pila casera que utiliza bitslice/PLD
- Arquitectura de computadoras de pila de segunda generación : tesis sobre la historia y el diseño de las máquinas de pila.
- Modelos de computación
- Máquinas apiladoras
- Microprocesadores