En informática , el código encadenado es una técnica de programación en la que el código tiene una estructura que consiste esencialmente en llamadas a subrutinas . Se utiliza con frecuencia en compiladores , que pueden generar código en esa forma o implementarlo ellos mismos. El código puede ser procesado por un intérprete o simplemente ser una secuencia de instrucciones de llamada a funciones en código máquina .
El código multihilo tiene mayor densidad que el código generado mediante técnicas de generación alternativas y convenciones de llamada alternativas . En arquitecturas con caché , puede ejecutarse ligeramente más lento. Sin embargo, un programa lo suficientemente pequeño como para caber en la caché del procesador puede ejecutarse más rápido que un programa más grande que sufre muchos fallos de caché . [ 1 ] Los programas pequeños también pueden ser más rápidos al cambiar de hilo , cuando otros programas han llenado la caché.
El código multihilo es más conocido por su uso en muchos compiladores de lenguajes de programación , como Forth , muchas implementaciones de BASIC , algunas implementaciones de COBOL , versiones tempranas de B , [ 2 ] y otros lenguajes para minicomputadoras pequeñas y para satélites de radioaficionados .
Historia
La forma habitual de crear programas informáticos es mediante un compilador que traduce el código fuente (escrito en algún lenguaje simbólico ) a código máquina . El ejecutable resultante suele ser rápido, pero, al ser específico de una plataforma de hardware , no es portable. Un enfoque diferente consiste en generar instrucciones para una máquina virtual y utilizar un intérprete en cada plataforma de hardware. El intérprete instancia el entorno de la máquina virtual y ejecuta las instrucciones. De este modo, el intérprete, compilado a código máquina, proporciona una capa de abstracción para los "lenguajes interpretados", que solo requieren una mínima compilación para adaptarse a dicha capa (la compilación puede limitarse a generar un árbol de sintaxis abstracta ) o incluso ninguna compilación (si la capa está diseñada para consumir código fuente sin procesar).
Los primeros ordenadores tenían relativamente poca memoria. Por ejemplo, la mayoría de los Data General Nova , los IBM 1130 y muchos de los primeros microordenadores solo tenían 4 kB de RAM instalada. En consecuencia, se invertía mucho tiempo en buscar maneras de reducir el tamaño de los programas para que cupieran en la memoria disponible.
Una solución es usar un intérprete que lea el lenguaje simbólico poco a poco y llame a funciones para realizar las acciones. Como el código fuente suele ser mucho más denso que el código máquina resultante, esto puede reducir el uso total de memoria. Esta fue la razón por la que Microsoft BASIC es un intérprete: [ a ] su propio código tenía que compartir los 4 kB de memoria de máquinas como la Altair 8800 con el código fuente del usuario. Un compilador traduce de un lenguaje fuente a código máquina, por lo que el compilador, el código fuente y la salida deben estar en memoria al mismo tiempo. En un intérprete, no hay salida.
El código encadenado es un estilo de formato para código compilado que minimiza el uso de memoria. En lugar de escribir cada paso de una operación en cada aparición del programa, como era común en los ensambladores de macros , el compilador escribe cada parte común del código en una subrutina. De esta forma, cada parte reside en un único lugar de la memoria (véase « No te repitas »). La aplicación de nivel superior en estos programas puede consistir únicamente en llamadas a subrutinas. Muchas de estas subrutinas, a su vez, también consisten únicamente en llamadas a subrutinas de nivel inferior.
Los mainframes y algunos microprocesadores antiguos, como el RCA 1802, requerían varias instrucciones para llamar a una subrutina. En la aplicación principal y en muchas subrutinas, esta secuencia se repite constantemente, y solo cambia la dirección de la subrutina entre una llamada y la siguiente. Esto significa que un programa con muchas llamadas a funciones también puede contener una cantidad considerable de código repetido.
Para solucionar esto, los sistemas de código multihilo utilizaban pseudocódigo para representar las llamadas a funciones en un solo operador. En tiempo de ejecución, un pequeño "intérprete" recorría el código de nivel superior, extraía la dirección de la subrutina en memoria y la llamaba. En otros sistemas, este mismo concepto básico se implementa como una tabla de bifurcaciones , una tabla de despacho o una tabla de métodos virtuales , todas las cuales consisten en una tabla de direcciones de subrutinas.
Durante la década de 1970, los diseñadores de hardware dedicaron un esfuerzo considerable a agilizar y simplificar las llamadas a subrutinas. En los diseños mejorados, solo se utiliza una instrucción para llamar a una subrutina, por lo que el uso de una pseudoinstrucción no ahorra espacio. Además, el rendimiento de estas llamadas prácticamente no genera sobrecarga. Hoy en día, si bien casi todos los lenguajes de programación se centran en aislar el código en subrutinas, lo hacen para mejorar la claridad y el mantenimiento del código, no para ahorrar espacio.
Los sistemas de código multihilo ahorran espacio al reemplazar esa lista de llamadas a funciones, donde solo cambia la dirección de la subrutina de una llamada a la siguiente, con una lista de tokens de ejecución, que son esencialmente llamadas a funciones sin el/los código(s) de operación de llamada, dejando solo una lista de direcciones. [ 3 ] [ 4 ] [ 5 ] [ 6 ] [ 7 ]
A lo largo de los años, los programadores han creado numerosas variantes de ese "intérprete" o "selector pequeño". La dirección específica de la lista de direcciones se puede extraer mediante un índice, un registro de propósito general o un puntero . Las direcciones pueden ser directas o indirectas, contiguas o no contiguas (vinculadas por punteros), relativas o absolutas, resueltas en tiempo de compilación o generadas dinámicamente. Ninguna variante es la "mejor" para todas las situaciones.
Desarrollo
Para ahorrar espacio, los programadores comprimieron las listas de llamadas a subrutinas en simples listas de direcciones de subrutinas y utilizaron un pequeño bucle para llamar a cada subrutina secuencialmente. Por ejemplo, el siguiente pseudocódigo utiliza esta técnica para sumar dos números A y B. En el ejemplo, la lista se denomina hilo y una variable ip (puntero de instrucción) indica nuestra posición dentro de la lista. Otra variable sp (puntero de pila) contiene una dirección en otra parte de la memoria que está disponible para almacenar un valor temporalmente.
inicio : ip = & hilo // apunta a la dirección '&pushA', no a la etiqueta textual 'hilo' arriba : saltar * ip ++ // seguir ip a la dirección en el hilo, seguir esa dirección a la subrutina, avanzar ip hilo : & pushA & pushB & agregar ... pushA : * sp ++ = A // seguir sp a la memoria disponible, almacenar A allí, avanzar sp al siguiente salto arriba pushB : * sp ++ = B saltar arriba agregar : sumencion1 = *-- sp // Extraer el valor superior de la pila sumencion2 = *-- sp // Extraer el segundo valor de la pila * sp ++ = sumencion1 + sumencion2 // Sumar los dos valores y almacenar el resultado en la parte superior de la pila saltar arriba El bucle de llamada topes tan simple que puede repetirse en línea al final de cada subrutina. El control ahora salta una sola vez, desde el final de una subrutina hasta el comienzo de otra, en lugar de saltar dos veces a través de top. Por ejemplo:
inicio : ip = & hilo // ip apunta a &pushA (que apunta a la primera instrucción de pushA) salto * ip ++ // envía el control a la primera instrucción de pushA y avanza ip a &pushB hilo : & pushA & pushB & agregar ... pushA : * sp ++ = A // sigue sp a la memoria disponible, almacena A allí, avanza sp al siguiente salto * ip ++ // envía el control donde ip dice (es decir, a pushB) y avanza ip pushB : * sp ++ = B salto * ip ++ agregar : sumencion1 = *-- sp // Extrae el valor superior de la pila sumencion2 = *-- sp // Extrae el segundo valor de la pila * sp ++ = sumencion1 + sumencion2 // Suma los dos valores y almacena el resultado en la parte superior de la pila salto * ip ++Esto se denomina código de subprocesos directos (DTC). Si bien la técnica es más antigua, el primer uso ampliamente difundido del término "código de subprocesos" probablemente se encuentre en el artículo de James R. Bell de 1973, "Threaded Code". [ 8 ]
En 1970, Charles H. Moore inventó una disposición más compacta, el código de subprocesos indirectos (ITC), para su máquina virtual Forth. Moore llegó a esta disposición porque las minicomputadoras Nova tenían un bit de indirección en cada dirección, lo que hacía que el ITC fuera fácil y rápido. Más tarde, dijo que le resultó tan conveniente que lo extendió a todos los diseños posteriores de Forth. [ 9 ]
Actualmente, algunos compiladores de Forth generan código de subprocesos directos, mientras que otros generan código de subprocesos indirectos. Los ejecutables se comportan igual en ambos casos.
Modelos de enhebrado
Prácticamente todo el código ejecutable multihilo utiliza uno u otro de estos métodos para invocar subrutinas (cada método se denomina "modelo de subprocesos").
Enhebrado directo
Las direcciones en el hilo son direcciones de lenguaje máquina. Esta forma es sencilla, pero puede generar sobrecarga, ya que el hilo consta únicamente de direcciones de máquina, por lo que todos los parámetros adicionales deben cargarse indirectamente desde la memoria. Algunos sistemas Forth generan código de encadenamiento directo. En muchas máquinas, el encadenamiento directo es más rápido que el encadenamiento mediante subrutinas (véase la referencia a continuación).
Un ejemplo de una máquina de pila podría ejecutar la secuencia "push A, push B, add". Eso podría traducirse al siguiente hilo y rutinas, donde ipse inicializa a la dirección etiquetada thread(es decir, la dirección donde &pushAse almacena).
#define PUSH(x) (*sp++ = (x)) #define POP() (*--sp) start : ip = & thread // ip apunta a &pushA (que apunta a la primera instrucción de pushA) jump * ip ++ // envía el control a la primera instrucción de pushA y avanza ip a &pushB thread : & pushA & pushB & add ... pushA : PUSH ( A ) jump * ip ++ // envía el control adonde dice ip (es decir, a pushB) y avanza ip pushB : PUSH ( B ) jump * ip ++ add : result = POP () + POP () PUSH ( result ) jump * ip ++Alternativamente, se pueden incluir operandos en el hilo. Esto puede eliminar parte de la indirección necesaria anteriormente, pero aumenta el tamaño del hilo:
#define PUSH(x) (*sp++ = (x)) #define POP() (*--sp) start : ip = & thread jump * ip ++ thread : & push & A // dirección donde se almacena A, no la A literal & push & B & add ... push : variable_address = * ip ++ // debe mover ip más allá de la dirección del operando, ya que no es una dirección de subrutina PUSH ( * variable_address ) // Lee el valor de la variable y lo inserta en la pila jump * ip ++ add : result = POP () + POP () PUSH ( result ) jump * ip ++Subprocesos indirectos
El encadenamiento indirecto utiliza punteros a ubicaciones que, a su vez, apuntan al código máquina. Al puntero indirecto pueden seguirle operandos que se almacenan en el "bloque" indirecto en lugar de almacenarlos repetidamente en el hilo. Por lo tanto, el código indirecto suele ser más compacto que el código de encadenamiento directo. La indirección generalmente lo hace más lento, aunque suele ser más rápido que los intérpretes de bytecode. Cuando los operandos del manejador incluyen tanto valores como tipos, el ahorro de espacio con respecto al código de encadenamiento directo puede ser significativo. Los sistemas FORTH más antiguos suelen generar código de encadenamiento indirecto.
Por ejemplo, si el objetivo es ejecutar "push A, push B, add", se podría usar lo siguiente. Aquí, ipse inicializa a la dirección &thread, cada fragmento de código ( push, add) se encuentra mediante doble indirección a través de ipy un bloque indirecto; y cualquier operando del fragmento se encuentra en el bloque indirecto que sigue a la dirección del fragmento. Esto requiere mantener la subrutina actualip en , a diferencia de todos los ejemplos anteriores donde contenía la siguiente subrutina que se iba a llamar.
inicio : ip = & hilo // apunta a '&i_pushA' salto ** ip // sigue los punteros a la primera instrucción de 'push', NO avanza ip todavía hilo : & i_pushA & i_pushB & i_add ... i_pushA : & push & A i_pushB : & push & B i_add : & add push : * sp ++ = ** ( * ip + 1 ) // mira 1 más allá del inicio del bloque indirecto para la dirección del operando salto * ( *++ ip ) // avanza ip en el hilo, salta a través del siguiente bloque indirecto a la siguiente subrutina agregar : addend1 = *-- sp addend2 = *-- sp * sp ++ = addend1 + addend2 salto * ( *++ ip )Subrutinas
El llamado "código enhebrado de subrutinas" (también llamado "código enhebrado de llamadas") consiste en una serie de instrucciones de "llamada" en lenguaje máquina (o direcciones de funciones a "llamar", a diferencia del uso de "jump" en la programación enhebrada directa). Los primeros compiladores para ALGOL , Fortran, Cobol y algunos sistemas Forth a menudo generaban código enhebrado de subrutinas. El código en muchos de estos sistemas operaba sobre una pila de operandos de último en entrar, primero en salir (LIFO), para la cual la teoría de compiladores estaba bien desarrollada. La mayoría de los procesadores modernos cuentan con soporte de hardware especial para las instrucciones de "llamada" y "retorno" de subrutinas, por lo que la sobrecarga de una instrucción de máquina adicional por despacho se reduce considerablemente.
Anton Ertl, cocreador del compilador Gforth , afirmó que, «contrariamente a los mitos populares, el enhebrado de subrutinas suele ser más lento que el enhebrado directo». [ 10 ] Sin embargo, las pruebas más recientes de Ertl [ 1 ] muestran que el enhebrado de subrutinas es más rápido que el enhebrado directo en 15 de 25 casos de prueba. Más concretamente, descubrió que el enhebrado directo es el modelo de enhebrado más rápido en los procesadores Xeon, Opteron y Athlon, el enhebrado indirecto es el más rápido en los procesadores Pentium M, y el enhebrado de subrutinas es el más rápido en los procesadores Pentium 4, Pentium III y PPC.
Como ejemplo de encadenamiento de llamadas para "push A, push B, add":
hilo : llamar a pushA llamar a pushB llamar a add ret pushA : * sp ++ = A ret pushB : * sp ++ = B ret add : addend1 = *-- sp addend2 = *-- sp * sp ++ = addend1 + addend2 retenhebrado de tokens
El código con enhebrado por token implementa el hilo como una lista de índices en una tabla de operaciones; el ancho del índice se elige naturalmente para que sea lo más pequeño posible para mayor densidad y eficiencia. 1 byte / 8 bits es la opción natural para facilitar la programación, pero se pueden usar tamaños más pequeños como 4 bits, o más grandes como 12 o 16 bits, dependiendo del número de operaciones admitidas. Siempre que el ancho del índice sea más estrecho que un puntero de máquina, será naturalmente más compacto que otros tipos de enhebrado sin mucho esfuerzo especial por parte del programador. Generalmente es de la mitad a tres cuartos del tamaño de otros enhebrados, que a su vez son de un cuarto a un octavo del tamaño del código sin enhebrado. Los punteros de la tabla pueden ser indirectos o directos. Algunos compiladores de Forth producen código con enhebrado por token. Algunos programadores consideran que el " código p " generado por algunos compiladores de Pascal , así como los códigos de bytes utilizados por .NET , Java , BASIC y algunos compiladores de C , son de tipo token-threading.
Históricamente, un enfoque común es el bytecode , que normalmente utiliza códigos de operación de 8 bits con una máquina virtual basada en pila. El intérprete de bytecode arquetípico se conoce como "intérprete de decodificación y despacho" y sigue la siguiente forma:
inicio : vpc = & hilo despacho : addr = decodificar ( & vpc ) // Convierte la siguiente operación de bytecode en un puntero al código máquina que la implementa // Cualquier operación entre instrucciones se realiza aquí (por ejemplo, actualización del estado global, procesamiento de eventos, etc.) salto addr CODE_PTR decodificar ( BYTE_CODE ** p ) { // En una codificación más compleja, puede haber varias tablas para elegir o indicadores de control/modo devolver tabla [ * ( * p ) ++ ]; } hilo : /* Contiene bytecode, no direcciones de máquina. Por lo tanto, es más compacto. */ 1 /*pushA*/ 2 /*pushB*/ 0 /*add*/ table : & add /* table[0] = dirección del código máquina que implementa el bytecode 0 */ & pushA /* table[1] ... */ & pushB /* table[2] ... */ pushA : * sp ++ = A salto de despacho pushB : * sp ++ = B salto de despacho add : addend1 = *-- sp addend2 = *-- sp * sp ++ = addend1 + addend2 salto de despachoSi la máquina virtual utiliza únicamente instrucciones de un byte, decode()se trata simplemente de una búsqueda desde thread, pero a menudo existen instrucciones de 1 byte de uso común, además de algunas instrucciones multibyte menos comunes (véase el conjunto de instrucciones complejo del ordenador ), en cuyo caso decode()es más complejo. La decodificación de los códigos de operación de un solo byte puede gestionarse de forma muy sencilla y eficiente mediante una tabla de bifurcación que utiliza el código de operación directamente como índice.
Para instrucciones con operaciones individuales sencillas, como "empujar" y "sumar", la sobrecarga que implica decidir qué ejecutar es mayor que el coste de ejecutarlo realmente, por lo que estos intérpretes suelen ser mucho más lentos que el código máquina. Sin embargo, para instrucciones más complejas ("compuestas"), el porcentaje de sobrecarga es proporcionalmente menos significativo.
En ocasiones, el código con subprocesos de token puede ejecutarse más rápido que el código máquina equivalente cuando este último resulta demasiado grande para caber en la caché de instrucciones L1 de la CPU. La mayor densidad de código del código con subprocesos, especialmente el de tokens, permite que quepa completamente en la caché L1, evitando así la saturación de la caché. Sin embargo, a diferencia del código máquina, que solo consume la caché de instrucciones, el código con subprocesos consume tanto la caché de instrucciones (para la implementación de cada operación) como la caché de datos (para el bytecode y las tablas); esto significa que el código con subprocesos reduce la cantidad de datos que la CPU puede procesar en un momento dado. En cualquier caso, si el problema que se está calculando implica aplicar un gran número de operaciones a una pequeña cantidad de datos, el uso de código con subprocesos puede ser una optimización ideal. [ 4 ]
Enhebrado de Huffman
El código enhebrado de Huffman consiste en listas de tokens almacenados como códigos de Huffman . Un código de Huffman es una cadena de bits de longitud variable que identifica un token único. Un intérprete enhebrado de Huffman localiza subrutinas mediante una tabla de índices o un árbol de punteros que puede ser navegado por el código de Huffman. El código enhebrado de Huffman es una de las representaciones más compactas conocidas para un programa informático. El índice y los códigos se eligen midiendo la frecuencia de las llamadas a cada subrutina en el código. A las llamadas frecuentes se les asignan los códigos más cortos. A las operaciones con frecuencias aproximadamente iguales se les asignan códigos con longitudes de bits casi iguales. La mayoría de los sistemas enhebrados de Huffman se han implementado como sistemas Forth de enhebrado directo y se han utilizado para empaquetar grandes cantidades de código de ejecución lenta en microcontroladores pequeños y económicos . La mayoría de los usos publicados [ 11 ] se han dado en tarjetas inteligentes, juguetes, calculadoras y relojes. El código tokenizado orientado a bits utilizado en PBASIC puede considerarse un tipo de código enhebrado de Huffman.
Roscado menos utilizado
Un ejemplo es el encadenamiento de cadenas, en el que las operaciones se identifican mediante cadenas, que normalmente se consultan en una tabla hash. Esto se utilizó en las primeras implementaciones de Forth de Charles H. Moore y en el lenguaje de programación experimental interpretado por hardware de la Universidad de Illinois . También se utiliza en Bashforth .
RPL
El RPL de HP , introducido por primera vez en la calculadora HP-18C en 1986, es un tipo de lenguaje interpretativo multihilo (TIL) híbrido (de subprocesos directos e indirectos) propietario [ 12 ] que, a diferencia de otros TIL, permite la incrustación de "objetos" RPL [ b ] en el "flujo de ejecución", es decir, el flujo de direcciones a través del cual avanza el puntero del intérprete. Un "objeto" RPL puede considerarse como un tipo de datos especial cuya estructura en memoria contiene una dirección a un "prólogo del objeto" al comienzo del objeto, y luego le siguen datos o código ejecutable. El prólogo del objeto determina cómo debe ejecutarse o procesarse el cuerpo del objeto. Usando el "bucle interno RPL" [ 13 ] , que fue inventado y patentado [ 14 ] por William C. Wickes en 1986 y publicado en 1988, la ejecución sigue así: [ 15 ]
- Desreferenciar el IP (puntero de instrucción) y almacenarlo en O (puntero de objeto actual).
- Incrementa la IP en la longitud de un puntero de dirección.
- Desreferencia O y almacena su dirección en O_1 (este es el segundo nivel de indirección).
- Transfiera el control al siguiente puntero u objeto incrustado configurando el PC (contador de programa) a O_1 más un puntero de dirección.
- Vuelve al paso 1
Esto se puede representar con mayor precisión mediante:
O = [I] I = I + Δ PC = [O] + Δ
Donde arriba, O es el puntero del objeto actual, I es el puntero del intérprete, Δ es la longitud de una palabra de dirección y el operador "[]" significa "desreferenciación".
Cuando el control se transfiere a un puntero de objeto o a un objeto incrustado, la ejecución continúa de la siguiente manera:
PRÓLOGO -> PRÓLOGO (La dirección del prólogo al inicio del código del prólogo apunta a sí misma) SI O + Δ =/= PC LUEGO IR A INDIRECTO (Prueba de ejecución directa) O = I - Δ (Corrección de O para que apunte al inicio del objeto incrustado) I = I + α (Corrección de I para que apunte después del objeto incrustado, donde α es la longitud del objeto) INDIRECTO (Resto del prólogo)
En los microprocesadores Saturn de HP que utilizan RPL, existe un tercer nivel de indirección posible gracias a un truco arquitectónico/de programación que permite una ejecución más rápida. [ 13 ]
Sucursales
En todos los intérpretes, una bifurcación simplemente cambia el puntero del hilo ( ip) a una dirección diferente dentro del hilo. Una bifurcación condicional de salto si cero, que salta solo si el valor de la cima de la pila es cero, podría implementarse como se muestra a continuación. Este ejemplo utiliza la versión de parámetros incrustados de la programación directa de hilos, por lo que la &thread[123]línea es el destino del salto si la condición es verdadera; por lo tanto, debe omitirse ( ip++) si no se toma la bifurcación.
hilo : ... & brz & hilo [ 123 ] ... brz : when_true_ip = * ip ++ // Obtener la dirección de destino para la bifurcación if ( *-- sp == 0 ) // Extraer/Consumir la parte superior de la pila y comprobar si es cero ip = when_true_ip jump * ip ++Servicios comunes
Separar las pilas de datos y de retorno en una máquina elimina gran parte del código de gestión de pilas, reduciendo sustancialmente el tamaño del código multihilo. El principio de doble pila se originó de forma independiente en tres ocasiones: para los grandes sistemas de Burroughs , Forth y PostScript . Se utiliza en algunas máquinas virtuales Java .
En una máquina virtual multihilo suelen estar presentes tres registros . Existe otro para pasar datos entre subrutinas («palabras»). Estos son:
- ip o i ( puntero de instrucción ) de la máquina virtual (que no debe confundirse con el contador de programa del hardware subyacente que implementa la máquina virtual).
- w (puntero de trabajo)
- rp o r (devuelve un puntero de pila )
- sp o s ( puntero de pila de parámetros para pasar parámetros entre palabras)
A menudo, las máquinas virtuales multihilo , como las implementaciones de Forth, tienen en su núcleo una máquina virtual simple, que consta de tres primitivas . Estas son:
- nido , también llamado docol
- desanidar , o semi_s (;s)
- próximo
En una máquina virtual de subprocesos indirectos, como la que se muestra aquí, las operaciones son:
siguiente : * ip ++ -> w salto ** w ++ anidar : ip -> * rp ++ w -> ip siguiente desanidar : *-- rp -> ip siguienteVéase también
- Estilo de paso de continuaciones , que reemplaza la variable global
ipcon un parámetro de función. - Compilación justo a tiempo
- Programación orientada a retorno : el redescubrimiento de código multihilo con el fin de explotar sistemas remotos vulnerables.
- Llamada de cola
- Historia de las CPU de propósito general
Notas
- ↑ Dartmouth BASIC , en el quese basa en última instancia Microsoft BASIC , era un compilador que se ejecutaba en máquinas mainframe.
- ↑ No confundir con los objetos asociados a la programación orientada a objetos.
Referencias
- 1 2 "Velocidad de diversas técnicas de envío de intérpretes V2" .
- ↑ Dennis M. Ritchie, "El desarrollo del lenguaje C" , 1993. Cita: "El compilador B del PDP-7 no generaba instrucciones de máquina, sino 'código encadenado'..."
- ↑ David Frech. "muforth readme" . Sección "Compilador nativo simple y recursivo de cola".
- 1 2 Steve Heller. "Programación eficiente en C/C++: más pequeña, más rápida, mejor" . 2014. Capítulo 5: "¿Necesitas un intérprete?" pág. 195.
- ↑ Jean-Paul Tremblay; PG Sorenson. "Teoría y práctica de la escritura de compiladores" . 1985. pág. 527
- ↑ "El mundo inalámbrico: electrónica, radio, televisión, volumen 89" . pág. 73.
- ↑ "Byte, Volumen 5" . 1980. pág. 212
- ↑ Bell, James R. (1973). "Código encadenado" . Communications of the ACM . 16 (6): 370– 372. doi : 10.1145/362248.362270 . S2CID 19042952 .
- ↑ Moore, Charles H., publicó comentarios en el cuarto número de la revista Byte.
- ↑ Ertl, Anton. "¿Qué es el código multihilo?" .
- ↑ Latendresse, Mario; Feeley, Marc. Generación de intérpretes rápidos para código de bytes comprimido Huffman . Elsevier. CiteSeerX 10.1.1.156.2546 .
- ↑ Loelinger, RG (1981) [agosto de 1979]. Escrito en Dayton, Ohio, EE. UU. Lenguajes interpretativos enhebrados: su diseño e implementación (2.ª impresión, 1.ª ed.). Peterborough, New Hampshire, Reino Unido: BYTE Books , BYTE Publications Inc. ISBN 0-07038360-XLCCN 80-19392 . ISBN 978-0-07038360-9. Consultado el 3 de agosto de 2023 .(xiv+2+251 páginas)
- 1 2 Busby, Jonathan (2018-09-07). "El bucle interno de RPL explicado" . El Museo de Calculadoras HP . Archivado del original el 2023-08-03 . Recuperado el 2019-12-27 .
- ↑ Wickes, William C. (1986-05-30). "Sistema y método de procesamiento de datos para la ejecución directa e indirecta de tipos de objetos estructurados uniformemente" . uspto.gov . Recuperado el 27 de diciembre de 2019 .
- ↑ Wickes, William C. (1988-10-01) [14–18 de junio de 1988]. Forsely, Lawrence P. (ed.). RPL: Un lenguaje de control matemático . Actas de la Conferencia Forth de Rochester de 1988: Entornos de programación. Vol. 8. Rochester, Nueva York, EE. UU.: Institute for Applied Forth Research, Inc., Universidad de Rochester . ISBN 978-0-91459308-9OCLC 839704944 (Nota: Este título se cita a menudo como "RPL: Un lenguaje de control matemático". Un extracto está disponible en: RPLMan del archivo ZIP del disco 4 de Goodies ).
Lecturas adicionales
- El libro "El desarrollo del lenguaje C", archivado el 28 de marzo de 2015 en Wayback Machine por Dennis M. Ritchie, describe B (un precursor de C) como implementado mediante "código encadenado".
- Horn, Joseph K. "¿Qué es RPL?" . Archivado del original el 17 de septiembre de 2017. Recuperado el 17 de septiembre de 2017 .(Nota: Breve descripción general de los lenguajes multihilo, RPL de sistema y de usuario, utilizados en las calculadoras HP como la HP 48 ).
Enlaces externos
- La página explicativa de Anton Ertl, " ¿Qué es el código multihilo?" , describe diferentes técnicas de multihilo y proporciona referencias adicionales.
- El proyecto Thinking Forth incluye el libro fundamental (pero agotado) Thinking Forth de Leo Brodie, archivado el 13 de noviembre de 2005 en Wayback Machine y publicado en 1984.
- Starting FORTH versión en línea del libro Starting FORTH de Leo Brodie Archivado el 13/11/2005 en Wayback Machine publicado en 1981.
- El libro de Brad Rodriguez, Moving FORTH: Part 1: Design Decisions in the Forth Kernel, analiza en profundidad las técnicas de encadenamiento de hilos.
- Extensiones de GCC. Etiquetas como valores.
- Compiladores
- Implementación del lenguaje de programación
- Máquinas virtuales basadas en pila