En programación informática , una máquina de código p ( máquina de código portátil [1] ) es una máquina virtual diseñada para ejecutar código p (el lenguaje ensamblador o código de máquina de una unidad central de procesamiento (CPU) hipotética). Este término se aplica tanto de forma genérica a todas esas máquinas (como la máquina virtual Java (JVM) y el código precompilado de MATLAB ) como a implementaciones específicas. Una de las más famosas es la p-máquina del sistema Pascal-P , en particular la implementación de Pascal de UCSD , entre cuyos desarrolladores la p en código p se interpretó como pseudo más a menudo que portable , por lo que pseudocódigo significa instrucciones para una pseudomáquina.
Aunque el concepto se implementó por primera vez alrededor de 1966 como código O para el lenguaje de programación combinado básico ( BCPL ) y código P para el lenguaje Euler , [2] el término código p apareció por primera vez a principios de la década de 1970. Los dos primeros compiladores que generaron código p fueron el compilador Pascal-P en 1973, de Kesav V. Nori, Urs Ammann, Kathleen Jensen, Hans-Heinrich Nägeli y Christian Jacobi, [3] y el compilador Pascal-S en 1975, de Niklaus Wirth .
Los programas que se han traducido a código p pueden ser interpretados por un programa de software que emula el comportamiento de la CPU hipotética, o traducidos al código de máquina de la CPU en la que se ejecutará el programa y luego ejecutados. Si existe suficiente interés comercial, se puede construir una implementación de hardware de la especificación de la CPU (por ejemplo, Pascal MicroEngine o una versión de un procesador Java ).
Beneficios y debilidades de la implementación de código p
A diferencia de la traducción directa al código de máquina nativo , un enfoque de dos etapas que implica la traducción al código p y la ejecución mediante interpretación o compilación justo a tiempo (JIT) ofrece varias ventajas.
- Es mucho más fácil escribir un pequeño intérprete de código p para una nueva máquina que modificar un compilador para generar código nativo para la misma máquina.
- La generación de código de máquina es una de las partes más complicadas de la escritura de un compilador. En comparación, la generación de código p es mucho más sencilla porque no se debe tener en cuenta ningún comportamiento dependiente de la máquina al generar el código de bytes . Esto lo hace útil para poner en funcionamiento un compilador rápidamente.
- Dado que el código p se basa en una máquina virtual ideal, un programa de código p suele ser mucho más pequeño que el mismo programa traducido a código de máquina.
- Cuando se interpreta el código p, el intérprete puede aplicar comprobaciones de tiempo de ejecución adicionales que son difíciles de implementar con código nativo.
Una de las desventajas más importantes del código P es la velocidad de ejecución, que a veces se puede corregir mediante la compilación Just-in-time . El código P también es más fácil de aplicar ingeniería inversa que el código nativo.
Implementaciones de código p
A principios de la década de 1980, al menos dos sistemas operativos lograron independencia de la máquina mediante el uso extensivo de código p. El Business Operating System (BOS) era un sistema operativo multiplataforma diseñado para ejecutar programas de código p exclusivamente. El UCSD p-System , desarrollado en la Universidad de California en San Diego, era un sistema operativo autocompilado y autoalojado [ aclaración necesaria ] basado en código p optimizado para su generación mediante el lenguaje Pascal .
En la década de 1990, la traducción a código p se convirtió en una estrategia popular para las implementaciones de lenguajes como Python , Microsoft P-Code en Visual Basic y Java bytecode en Java .
El lenguaje Go utiliza un ensamblador genérico y portable como una forma de código p, implementado por Ken Thompson como una extensión del trabajo en Plan 9 de Bell Labs . A diferencia del bytecode de Common Language Runtime (CLR) o el bytecode de JVM, no hay una especificación estable, y las herramientas de compilación de Go no emiten un formato de bytecode para ser utilizado en un momento posterior. El ensamblador de Go utiliza el lenguaje ensamblador genérico como una representación intermedia , y los ejecutables de Go son binarios enlazados estáticamente específicos de la máquina . [4]
Máquina p de la UCSD
Arquitectura
Al igual que muchas otras máquinas de código p, la UCSD p-Machine es una máquina de pila , lo que significa que la mayoría de las instrucciones toman sus operandos de una pila y colocan los resultados nuevamente en la pila. Por lo tanto, la addinstrucción reemplaza los dos elementos superiores de la pila con su suma. Algunas instrucciones toman un argumento inmediato. Al igual que Pascal, el código p está fuertemente tipado y admite tipos de datos booleanos (b), de caracteres (c), enteros (i), reales (r), conjuntos (s) y punteros (a) de forma nativa.
Algunas instrucciones sencillas:
Insn. Stack Descripción de la pila
antes después
adi i1 i2 i1+i2 suma dos enteros
adr r1 r2 r1+r2 sumar dos reales
inn i1 s1 b1 conjunto de pertenencia; b1 = si i1 es miembro de s1
ldi i1 i1 i1 carga constante entera
movimiento a1 a2 a2 movimiento
no b1 b1 -b1 negación booleana
Ambiente
De manera similar a una CPU de destino real, el p-System tiene solo una pila compartida por los marcos de pila de procedimientos (que proporcionan la dirección de retorno , etc.) y los argumentos de las instrucciones locales. Tres de los registros de la máquina apuntan a la pila (que crece hacia arriba):
- SP apunta a la parte superior de la pila (el puntero de pila ).
- MP marca el comienzo del marco de pila activo (el puntero de marca ).
- EP apunta a la ubicación de pila más alta utilizada en el procedimiento actual (el puntero extremo).
También hay un área constante y, debajo de ella, el montón que crece hacia abajo en dirección a la pila. El registro NP (el nuevo puntero) apunta a la parte superior (la dirección más baja utilizada) del montón. Cuando EP es mayor que NP, la memoria de la máquina se agota.
El quinto registro, PC, apunta a la instrucción actual en el área de código.
Convenciones de llamadas
Los marcos de pila se ven así:
EP ->
pila local
SP -> ...
lugareños
...
parámetros
...
Dirección de retorno (PC anterior)
EP anterior
Enlace dinámico (MP anterior)
Enlace estático (MP del procedimiento circundante)
MP -> valor de retorno de la función
La secuencia de llamada al procedimiento funciona de la siguiente manera: La llamada se introduce con
mst n
donde nespecifica la diferencia en los niveles de anidamiento (recuerde que Pascal admite procedimientos anidados). Esta instrucción marcará la pila, es decir, reservará las primeras cinco celdas del marco de pila anterior e inicializará el EP anterior, el enlace dinámico y estático. Luego, el llamador calcula e introduce cualquier parámetro para el procedimiento y luego emite
taza n, p
para llamar a un procedimiento de usuario ( nsiendo el número de parámetros pla dirección del procedimiento). Esto guardará la PC en la celda de dirección de retorno y establecerá la dirección del procedimiento como la nueva PC.
Los procedimientos de usuario comienzan con las dos instrucciones
ent 1, yo ent 2, j
El primero establece SP en MP+ i, el segundo establece EP en SP+ j. iBásicamente, especifica el espacio reservado para variables locales (más el número de parámetros más 5) y jproporciona el número de entradas necesarias localmente para la pila. En este punto, se comprueba el agotamiento de la memoria.
La devolución de la llamada se realiza a través de
retC
con Cel tipo de retorno (i, r, c, b, a como se indica arriba, y p para ningún valor de retorno). El valor de retorno debe almacenarse previamente en la celda correspondiente. En todos los tipos excepto p, el retorno dejará este valor en la pila.
En lugar de llamar a un procedimiento de usuario (cup), qse puede llamar a un procedimiento estándar con
Csp q
Estos procedimientos estándar son procedimientos Pascal como readln()( csp rln), sin()( csp sin), etc. Curiosamente, eof()en su lugar se trata de una instrucción de código p.
Ejemplo de máquina
Niklaus Wirth especificó una máquina de código p simple en el libro Algoritmos + Estructuras de datos = Programas de 1976. La máquina tenía 3 registros: un contador de programa p , un registro base b y un registro superior t . Había 8 instrucciones:
lit 0, a: carga constante aopr 0, a: ejecutar la operación a (13 operaciones: RETORNO, 5 funciones matemáticas y 7 funciones de comparación)lod l, a: variable de carga l , asto l, a: almacena la variable l , acal l, a: llamar al procedimiento a en el nivel lint 0, a:incrementar el registro t en ajmp 0, a: saltar a unjpc 0, a: salto condicional a un [5]
Este es el código de la máquina, escrito en Pascal:
const
amax = 2047 ; {dirección máxima} levmax = 3 ; {profundidad máxima de anidamiento de bloques} cxmax = 200 ; {tamaño de la matriz de códigos}
tipo
fct = ( lit , opr , lod , sto , cal , int , jmp , jpc ) ; instrucción = registro empaquetado f : fct ; l : 0 .. levmax ; a : 0 .. amax ; fin ;
var
código : matriz [ 0 .. cxmax ] de instrucción ;
procedimiento interpretar ;
constante tamaño de pila = 500 ;
var
p , b , t : entero ; {registros de programa, base y pila superior} i : instrucción ; {registro de instrucción} s : matriz [ 1 ... tamaño de pila ] de enteros ; {almacén de datos}
función base ( l : entero ) : entero ; var b1 : entero ; begin b1 := b ; {encontrar base l niveles hacia abajo} mientras l > 0 hacer begin b1 := s [ b1 ] ; l := l - 1 fin ; base := b1 fin {base} ;
empezar
writeln ( ' start pl/0' ) ; t := 0 ; b := 1 ; p := 0 ; s [ 1 ] := 0 ; s [ 2 ] := 0 ; s [ 3 ] := 0 ; repetir i := código [ p ] ; p := p + 1 ; con i hacer caso f de lit : empezar t := t + 1 ; s [ t ] := a fin ; opr : caso a de {operador} 0 : empezar {retorno} t := b - 1 ; p := s [ t + 3 ] ; b := s [ t + 2 ] ; fin ; 1 : s [ t ] := - s [ t ] ; 2 : inicio t := t - 1 ; s [ t ] := s [ t ] + s [ t + 1 ] fin ; 3 : inicio t := t - 1 ; s [ t ] := s [ t ] - s [ t + 1 ] fin ; 4 : inicio t := t - 1 ; s [ t ] := s [ t ] * s [ t + 1 ] fin ;
5 : inicio t := t - 1 ; s [ t ] := s [ t ] div s [ t + 1 ] fin ; 6 : s [ t ] := orden ( impar ( s [ t ])) ; 8 : inicio t := t - 1 ; s [ t ] := orden ( s [ t ] = s [ t + 1 ]) fin ; 9 : inicio t := t - 1 ; s [ t ] := orden ( s [ t ] <> s [ t + 1 ]) fin ; 10 : inicio t := t - 1 ; s [ t ] := orden ( s [ t ] < s [ t + 1 ]) fin ; 11 : inicio t := t - 1 ; s [ t ] := orden ( s [ t ] >= s [ t + 1 ]) fin ; 12 : inicio t := t - 1 ; s [ t ] := orden ( s [ t ] > s [ t + 1 ]) fin ; 13 : inicio t := t - 1 ; s [ t ] := orden ( s [ t ]
<= s [ t + 1 ]) fin ; fin ; lod : comienzo t := t + 1 ; s [ t ] := s [ base ( l ) + a ] fin ; sto : comienzo s [ base ( l ) + a ] := s [ t ] ; writeln ( s [ t ]) ; t := t - 1 fin ; cal : comienzo {generar nueva marca de bloque} s [ t + 1 ] := base ( l ) ; s [ t + 2 ] := b ; s [ t + 3 ] := p ; b := t + 1 ; p := a fin ; int : t := t + a ; jmp : p := a ; jpc : comienza si s [ t ] = 0 entonces p := a ; t := t - 1 fin fin {con, caso} hasta p = 0 ; writeln ( ' fin pl/0' ) ; fin {interpretar} ;
Esta máquina se utilizó para ejecutar el PL/0 de Wirth , un compilador de subconjuntos de Pascal utilizado para enseñar el desarrollo de compiladores. [6] [ verificación fallida ]
Código P de Microsoft
P-Code es el nombre de varios de los lenguajes intermedios patentados de Microsoft . Proporcionaban un formato binario alternativo al código de máquina . En varias ocasiones, Microsoft ha dicho que p-code es una abreviatura de código empaquetado [7] o pseudocódigo [8] .
El código p de Microsoft se utilizó en Visual C++ y Visual Basic . Al igual que otras implementaciones de código p, el código p de Microsoft permitió un ejecutable más compacto a costa de una ejecución más lenta.
Otras implementaciones
Véase también
- Código de bytes
- Representación intermedia
- Joel McCormack , diseñador de la versión de la máquina de código p de NCR Corporation
- Sistema de ejecución
- Enhebrado de tokens
- Código mnemotécnico de City & Guilds
- Modelo independiente de la plataforma
Referencias
- ^ Upton, Eben; Duntemann, Jeffrey; Roberts, Ralph; Mamtora, Tim; Everard, Ben (13 de septiembre de 2016). Aprendiendo arquitectura informática con Raspberry Pi. John Wiley & Sons. ISBN 978-1-119-18393-8.
- ^ Wirth, Niklaus ; Weber, Helmut (1966). "EULER: una generalización de ALGOL y su definición formal: Parte II". Comunicaciones de la ACM . 9 (2). Nueva York, EE. UU.: Association for Computing Machinery (ACM): 89–99. doi : 10.1145/365170.365202 . S2CID 12124100.
- ^ Nori, Kesav V.; Ammann, Urs; Jensen, Kathleen; Nägeli, Hans-Heinrich; Jacobi, cristiano (1975). Notas de implementación del compilador Pascal P. Zúrich, Suiza: Eidgenössische Technische Hochschule (ETH).
- ^ Pike, Robert C. (2016). "El diseño del ensamblador Go". YouTube (charla en conferencia). Archivado desde el original el 2021-12-11 . Consultado el 2017-08-25 .
- ^ "Archivos de la categoría: Wirth - Euler - Diseñado por Niklaus Wirth y Helmut Weber". Pascal para máquinas pequeñas - Lenguajes Wirth, Pascal, UCSD, Turbo, Delphi, Freepascal, Oberon . 2018-08-02.
- ^ Alpert, Donald (septiembre de 1979). Un intérprete de código P de Pascal para el premio Emmy de Stanford (PDF) (informe). Laboratorio de sistemas informáticos, Departamentos de ingeniería eléctrica y ciencias de la computación, Universidad de Stanford. Nota técnica n.º 164.
- ^ Padawer, Andy (abril de 1992). "Tecnología Microsoft P-Code". Microsoft Developer Network . Archivado desde el original el 22 de febrero de 2001.
- ^ "Compilar su proyecto en código nativo". Documentación de Visual Studio 6.0 . 2007. Archivado desde el original el 27 de febrero de 2007.
Lectura adicional
- Pemberton, Steven ; Daniels, Martin. Implementación de Pascal: el compilador e intérprete P4. John Wiley . ISBN 0-13-653031-1.
- Pemberton, Steven , ed. (13 de abril de 2011). "Implementación de Pascal: un libro y fuentes".(NB. Tiene fuentes Pascal del compilador e intérprete P4 , instrucciones de uso).
- Pemberton, Steven , ed. (13 de abril de 2011). "pcode del compilador Pascal compilado por sí mismo".(NB. Tiene el código p del compilador P4 , generado por él mismo.)
- "La página del Museo de Computación Jefferson sobre el p-System de la UCSD".
- "Implementación de código abierto"., incluyendo paquetes y binarios precompilados; una bifurcación amigable de Klebsch. "Implementación de Klebsch".
- Terry, Pat (2005). Compilación con C# y Java . Pearson/Addison-Wesley. pág. 624. ISBN 0-321-26360-X.
- Wirth, Niklaus (1975). Algoritmos + Estructuras de datos = Programas . Prentice-Hall. ISBN 0-13-022418-9.
- Wirth, Niklaus (1996). Construcción de compiladores . Addison-Wesley. ISBN 0-201-40353-6.
- Liffick, Blaise W., ed. (1979). El libro de bytes de Pascal . Publicaciones BYTE. ISBN 0-07-037823-1.
- Barron, David William , ed. (1981). Pascal: el lenguaje y su implementación . Wiley. ISBN 0-471-27835-1.(NB. Consulte especialmente los artículos Notas de implementación de Pascal-P y Pascal-S: un subconjunto y su implementación ).
Enlaces externos
- Información sobre el código P de VB por Mr Silver en Wayback Machine (archivado el 22 de diciembre de 2015)