El conjunto de instrucciones Burroughs B6x00-7x00 incluye el conjunto de operaciones válidas para los sistemas grandes Burroughs B6500, [1] B7500 y posteriores , incluidos los sistemas Unisys Clearpath/MCP actuales (a partir de 2006) ; no incluye las instrucciones para otros sistemas grandes Burroughs, incluidos los B5000, B5500, B5700 y B8500. Estas máquinas únicas tienen un diseño y un conjunto de instrucciones distintivos. Cada palabra de datos está asociada con un tipo, y el efecto de una operación en esa palabra puede depender del tipo. Además, las máquinas están basadas en pila [a] hasta el punto de que no tenían registros direccionables por el usuario.
Como se esperaría de la arquitectura única utilizada en estos sistemas, también tienen un conjunto de instrucciones interesante . Los programas se componen de sílabas de 8 bits , que pueden ser Name Call, Value Call o formar un operador, que puede tener de una a doce sílabas de longitud. Hay menos de 200 operadores , todos los cuales caben en sílabas de 8 bits. Si ignoramos los poderosos operadores de escaneo de cadenas, transferencia y edición, el conjunto básico es de solo unos 120 operadores. Si eliminamos los operadores reservados para el sistema operativo, como MVST y HALT, el conjunto de operadores comúnmente utilizados por los programas de nivel de usuario es menos de 100. Las sílabas Name Call y Value Call contienen parejas de direcciones; las sílabas Operator no usan direcciones o usan palabras de control y descriptores en la pila.
Dado que no hay registros direccionables por el programador, la mayoría de las operaciones de manipulación de registros requeridas en otras arquitecturas no son necesarias, ni tampoco las variantes para realizar operaciones entre pares de registros , ya que todas las operaciones se aplican a la parte superior de la pila . Esto también hace que los archivos de código sean muy compactos, ya que los operadores tienen una dirección cero y no necesitan incluir la dirección de los registros o las ubicaciones de memoria en el flujo de código. Parte de la densidad del código se debió a mover información vital de los operandos a otra parte, a "etiquetas" en cada palabra de datos o en tablas de punteros. Muchos de los operadores son genéricos o polimórficos según el tipo de datos sobre los que se actúa según lo indicado por la etiqueta. Los códigos de operación genéricos requerían menos bits de código de operación pero hacían que el hardware fuera más como un intérprete, con menos oportunidades de canalizar los casos comunes.
Por ejemplo, el conjunto de instrucciones tiene solo un operador ADD. Tuvo que buscar el operando para descubrir si se trataba de una suma de números enteros o de coma flotante. Las arquitecturas típicas requieren múltiples operadores para cada tipo de datos, por ejemplo, add.i, add.f, add.d, add.l para los tipos de datos enteros, flotantes, dobles y largos. La arquitectura solo distingue números de precisión simple y doble: los números enteros son simplemente números reales con un exponente cero . Cuando uno o ambos operandos tienen una etiqueta de 2, se realiza una suma de precisión doble; de lo contrario, la etiqueta 0 indica precisión simple. Por lo tanto, la etiqueta en sí es el equivalente de la extensión del operador .i, .f, .d y .l. Esto también significa que el código y los datos nunca pueden estar desparejos.
Hay dos operadores importantes en el manejo de datos en la pila: Value Call (VALC) y Name Call (NAMC). Se trata de operadores de dos bits, donde 00 es VALC y 01 es NAMC. Los siguientes seis bits de la sílaba, concatenados con la sílaba siguiente, proporcionan la pareja de direcciones. Por lo tanto, VALC cubre los valores de sílaba 0000 a 3FFF y NAMC 4000 a 7FFF.
VALC es otro operador polimórfico. Si encuentra una palabra de datos, esa palabra se carga en la parte superior de la pila . Si encuentra una IRW, se la sigue, posiblemente en una cadena de IRW hasta que se encuentra una palabra de datos. Si se encuentra una PCW, se ingresa una función para calcular el valor y el VALC no se completa hasta que la función regresa.
NAMC simplemente carga el par de direcciones en la parte superior de la pila como un IRW (con la etiqueta establecida automáticamente en 1).
Las ramas estáticas (BRUN, BRFL y BRTR) utilizaban dos sílabas adicionales de desplazamiento. Por lo tanto, las operaciones aritméticas ocupaban una sílaba, las operaciones de direccionamiento (NAMC y VALC) ocupaban dos, las ramas tres y los literales largos (LT48) cinco. Como resultado, el código era mucho más denso (tenía mejor entropía) que una arquitectura RISC convencional en la que cada operación ocupaba cuatro bytes. Una mejor densidad de código significaba menos errores de caché de instrucciones y, por lo tanto, un mejor rendimiento al ejecutar código a gran escala.
En las siguientes explicaciones de los operadores, recuerde que A y B son los dos registros superiores de la pila. Las extensiones de doble precisión las proporcionan los registros X e Y; por lo tanto, los dos operandos superiores de doble precisión se dan mediante AX y BY. (En la mayoría de los casos, AX y BY están implícitos en A y B).
En el B6500, una palabra tiene 48 bits de datos y tres bits de etiqueta. Se extiende a tres bits fuera de la palabra de 48 bits en una etiqueta. Los bits de datos son los bits 0 a 47 y la etiqueta está en los bits 48 a 50. El bit 48 es el bit de solo lectura, por lo que las etiquetas impares indican palabras de control que no pueden ser escritas por un programa de nivel de usuario. Las palabras de código reciben la etiqueta 3. A continuación, se incluye una lista de las etiquetas y su función:
La versión actual de estas máquinas, Unisys ClearPath, ha ampliado las etiquetas hasta convertirlas en etiquetas de cuatro bits. El nivel de microcódigo que especificaba las etiquetas de cuatro bits se denominaba nivel Gamma.
Las palabras con etiquetas pares son datos de usuario que pueden ser modificados por un programa de usuario como estado de usuario. Las palabras con etiquetas impares son creadas y utilizadas directamente por el hardware y representan el estado de ejecución de un programa. Dado que estas palabras son creadas y consumidas por instrucciones específicas o por el hardware, el formato exacto de estas palabras puede cambiar entre la implementación del hardware y los programas de usuario no necesitan ser recompilados, ya que el mismo flujo de código producirá los mismos resultados, aunque el formato de las palabras del sistema pueda haber cambiado.
Las palabras de la etiqueta 1 representan direcciones de datos en la pila. El IRW normal simplemente almacena una dirección asociada a los datos en la pila actual. El SIRW hace referencia a los datos en cualquier pila incluyendo un número de pila en la dirección.
Las palabras de la etiqueta 5 son descriptores, que se describen con más detalle en la siguiente sección. Las palabras de la etiqueta 5 representan direcciones de datos fuera de la pila.
La etiqueta 7 es la palabra de control del programa que describe un punto de entrada de procedimiento. Cuando los operadores llegan a una PCW, se ingresa al procedimiento. El operador ENTR ingresa explícitamente a un procedimiento (rutina que no devuelve valor). Las funciones (rutinas que devuelven valor) son ingresadas implícitamente por operadores como la llamada de valor (VALC). Las rutinas globales se almacenan en el entorno D[2] como SIRW que apuntan a una PCW almacenada en el diccionario de segmentos de código en el entorno D[1]. El entorno D[1] no se almacena en la pila actual porque puede ser referenciado por todos los procesos que comparten este código. Por lo tanto, el código es reentrante y compartido.
La etiqueta 3 representa las palabras de código en sí, que no aparecerán en la pila. La etiqueta 3 también se utiliza para las palabras de control de pila MSCW, RCW y TOSCW.
Una optimización de hardware de pila es la provisión de registros D (o "de visualización"). Estos son registros que apuntan al inicio de cada marco de pila. Estos registros se actualizan automáticamente a medida que se ingresan y salen de los procedimientos y no son accesibles por ningún software. Hay 32 registros D, lo que limita a 32 niveles de anidamiento léxico.
Consideremos cómo accederíamos a una variable global de nivel léxico 2 (D[2]) desde el nivel léxico 5 (D[5]). Supongamos que la variable está a 6 palabras de la base del nivel léxico 2. Por lo tanto, está representada por el par de direcciones (2, 6). Si no tenemos D registros, tenemos que mirar la palabra de control en la base del marco D[5], que apunta al marco que contiene el entorno D[4]. Luego miramos la palabra de control en la base de este entorno para encontrar el entorno D[3] y continuamos de esta manera hasta que hayamos seguido todos los enlaces de regreso al nivel léxico requerido. Esta no es la misma ruta que la ruta de retorno a través de los procedimientos que se han llamado para llegar a este punto. (La arquitectura mantiene tanto la pila de datos como la pila de llamadas en la misma estructura, pero utiliza palabras de control para diferenciarlas).
Como puede ver, esto es bastante ineficiente simplemente para acceder a una variable. Con registros D, el registro D[2] apunta a la base del entorno de nivel léxico 2, y todo lo que necesitamos hacer para generar la dirección de la variable es sumar su desplazamiento desde la base del marco de la pila a la dirección base del marco en el registro D. (Existe un operador de búsqueda de lista enlazada eficiente LLLU, que podría buscar en la pila de la manera anterior, pero el enfoque del registro D seguirá siendo más rápido). Con registros D, el acceso a entidades en entornos externos y globales es tan eficiente como el acceso a variables locales.
Datos de la etiqueta D — Dirección de pareja, Comentariosregistro
| 0 | n | (4, 1) El entero n (declarado al ingresar a un bloque, no a un procedimiento)|-----------------------|| D[4]==>3 | MSCW | (4, 0) La palabra de control de pila de marcas que contiene el enlace a D[3].|========================|| 0 | r2 | (3, 5) El verdadero r2|-----------------------|| 0 | r1 | (3, 4) El verdadero r1|-----------------------|| 1 | p2 | (3, 3) Una referencia SIRW a g en (2,6)|-----------------------|| 0 | p1 | (3, 2) El parámetro p1 del valor de f |-----------------------|| 3 | RCW | (3, 1) Una palabra de control de retorno|-----------------------|| D[3]==>3 | MSCW | (3, 0) La palabra de control de pila de marcas que contiene el enlace a D[2].|========================|| 1 | a | (2, 7) La matriz a ======>[bloque de memoria de diez palabras]|-----------------------|| 0 | g | (2, 6) La verdadera g |-----------------------|| 0 | f | (2, 5) La f real |-----------------------|| 0 | k | (2, 4) El entero k |-----------------------|| 0 | j | (2, 3) El entero j |-----------------------|| 0 | i | (2, 2) El entero i|-----------------------|| 3 | RCW | (2, 1) Una palabra de control de retorno|-----------------------|| D[2]==>3 | MSCW | (2, 0) La palabra de control de pila de marcas que contiene el vínculo al marco de pila anterior.|========================| — Parte inferior de la pila
Si hubiéramos invocado el procedimiento p como una corrutina o una instrucción de proceso, el entorno D[3] se habría convertido en una pila separada basada en D[3]. Esto significa que los procesos asincrónicos aún tienen acceso al entorno D[2] como se implica en el código del programa ALGOL. Llevando esto un paso más allá, un programa totalmente diferente podría llamar al código de otro programa, creando un marco de pila D[3] que apunta al entorno D[2] de otro proceso sobre su propia pila de procesos. En un instante, todo el espacio de direcciones del entorno de ejecución del código cambia, haciendo que el entorno D[2] en la propia pila de procesos no sea directamente direccionable y, en cambio, haga que el entorno D[2] en otra pila de procesos sea directamente direccionable. Así es como se implementan las llamadas a la biblioteca. En una llamada entre pilas de este tipo, el código que llama y el código llamado podrían incluso originarse de programas escritos en diferentes lenguajes fuente y ser compilados por diferentes compiladores.
Los entornos D[1] y D[0] no se encuentran en la pila del proceso actual. El entorno D[1] es el diccionario de segmentos de código, que comparten todos los procesos que ejecutan el mismo código. El entorno D[0] representa las entidades exportadas por el sistema operativo.
En realidad, los marcos de pila ni siquiera tienen por qué existir en una pila de procesos. Esta característica se utilizó al principio para la optimización de E/S de archivos; el FIB (bloque de información de archivos) se vinculaba a los registros de visualización en D[1] durante las operaciones de E/S. A principios de los noventa, esta capacidad se implementó como una característica del lenguaje en forma de BLOQUES DE ESTRUCTURA y, combinada con tecnología de bibliotecas, como BLOQUES DE CONEXIÓN. La capacidad de vincular una estructura de datos al ámbito de direcciones del registro de visualización implementó la orientación a objetos. Por lo tanto, el B6500 utilizó realmente una forma de orientación a objetos mucho antes de que se utilizara el término.
En otros sistemas, el compilador podría construir su tabla de símbolos de una manera similar, pero eventualmente los requisitos de almacenamiento se cotejarían y el código de la máquina se escribiría para usar direcciones de memoria planas de 16 bits o 32 bits o incluso 64 bits. Estas direcciones podrían contener cualquier cosa, por lo que una escritura en la dirección incorrecta podría dañar cualquier cosa. En cambio, el esquema de direcciones de dos partes fue implementado por el hardware. En cada nivel léxico, las variables se colocaron en desplazamientos hacia arriba desde la base de la pila del nivel, ocupando típicamente una palabra - las variables de precisión doble o complejas ocuparían dos. Las matrices no se almacenaban en esta área, solo se almacenaba un descriptor de una palabra para la matriz. Por lo tanto, en cada nivel léxico, el requisito total de almacenamiento no era grande: docenas, cientos o algunos miles en casos extremos, ciertamente no un recuento que requiriera 32 bits o más. Y de hecho, esto se reflejó en la forma de la instrucción VALC (llamada de valor) que cargaba un operando en la pila. Este código de operación tenía dos bits de longitud y el resto de los bits del byte se concatenaban con el byte siguiente para dar un campo de direccionamiento de catorce bits. El código que se estaba ejecutando estaría en algún nivel léxico, digamos seis: esto significaba que solo eran válidos los niveles léxicos del cero al seis, y por lo tanto solo se necesitaban tres bits para especificar el nivel léxico deseado. La parte de dirección de la operación VALC reservaba así solo tres bits para ese propósito, y el resto estaba disponible para hacer referencia a entidades en ese nivel y niveles inferiores. Un procedimiento profundamente anidado (es decir, en un nivel léxico alto) tendría menos bits disponibles para identificar entidades: para el nivel dieciséis en adelante, se necesitarían cinco bits para especificar la elección de los niveles 0 a 31, dejando así nueve bits para identificar no más de las primeras 512 entidades de cualquier nivel léxico. Esto es mucho más compacto que direccionar entidades por su dirección de memoria literal en un espacio de direccionamiento de 32 bits. Además, solo el código de operación VALC cargó datos: los códigos de operación para ADD, MULT, etc., no realizaron direccionamiento y trabajaron completamente en los elementos superiores de la pila.
Mucho más importante es que este método significaba que muchos errores disponibles para los sistemas que empleaban direccionamiento plano no podían ocurrir porque eran simplemente indescriptibles incluso a nivel de código de máquina. Una tarea no tenía forma de corromper la memoria en uso por otra tarea, porque no tenía forma de desarrollar su dirección. Los desplazamientos de un registro D especificado serían comprobados por el hardware contra el límite del marco de pila: los valores no autorizados serían atrapados. De manera similar, dentro de una tarea, un descriptor de matriz contenía información sobre los límites de la matriz, y por lo tanto cualquier operación de indexación era comprobada por el hardware: dicho de otra manera, cada matriz formaba su propio espacio de direcciones. En cualquier caso, el etiquetado de todas las palabras de memoria proporcionaba un segundo nivel de protección: una asignación errónea de un valor solo podía ir a una ubicación que contuviera datos, no a una que contuviera un puntero o un descriptor de matriz, etc. y ciertamente no a una ubicación que contuviera código de máquina.
— seguido de NAMC para cargar el PCW, luego se introducen los parámetros según sea necesario y luego ENTR)
— almacenar el valor en el registro B en la memoria direccionada por el registro A. — eliminar el valor de la pila.
La instrucción Load podría encontrarse disparada por una dirección indirecta o, peor aún, por una llamada disfrazada a una rutina thunk de llamada por nombre .
—Siga una cadena de direcciones si es necesario.
Estos se utilizaban para transferencias de cadenas, generalmente hasta que se detectaba un determinado carácter en la cadena de origen. Todos estos operadores están protegidos contra desbordamientos de búfer al estar limitados por los límites de los descriptores.
Estos se utilizaban para escanear cadenas útiles para escribir compiladores . Todos estos operadores están protegidos contra desbordamientos de búfer al estar limitados por los límites de los descriptores.
Se trataba de operadores especiales para la manipulación sofisticada de cadenas, especialmente para aplicaciones comerciales.