Tutorial/recurso para implementar VM
vm-implementation (6)
Quiero un propósito de autoeducación, implementar una máquina virtual simple para un lenguaje dinámico, prefiero en C. Algo como el Lua VM, o Parrot, o Python VM, pero más simple. ¿Existen buenos recursos / tutoriales para lograr esto, además de analizar el código y las documentaciones de diseño de las máquinas virtuales existentes?
Editar: ¿por qué cerrar la votación? No entiendo - esto no es programación. Por favor comente si hay un problema específico con mi pregunta.
Al igual que usted, también he estado estudiando máquinas virtuales y compiladores y un buen libro que puedo recomendar es Diseño de compiladores: Máquinas virtuales . Describe las máquinas virtuales para lenguajes imperativos, funcionales, lógicos y orientados a objetos al proporcionar el conjunto de instrucciones para cada VM junto con un tutorial sobre cómo compilar un lenguaje de nivel superior para esa VM. Solo he implementado la máquina virtual para el lenguaje imperativo y ya ha sido un ejercicio muy útil.
Si está empezando, otro recurso que puedo recomendar es PL101 . Es un conjunto interactivo de lecciones en JavaScript que lo guía a través del proceso de implementación de analizadores e intérpretes para varios idiomas.
Bueno, no se trata de implementar una VM en C, pero como era la última pestaña que tenía abierta antes de ver esta pregunta, siento que necesito señalar un artículo sobre la implementación de un compilador de bytecode QBASIC y una máquina virtual en JavaScript usando el <canvas>
etiqueta para mostrar. Incluye todo el código fuente para obtener suficiente de QBASIC implementado para ejecutar el juego "nibbles", y es el primero de una serie de artículos sobre el compilador y el intérprete de bytecode; éste describe la máquina virtual, y también promete futuros artículos que describen el compilador.
Por cierto, no voté para cerrar tu pregunta, pero la votación cercana que obtuviste fue como duplicado de una pregunta del año pasado sobre cómo aprender a implementar una máquina virtual. Creo que esta pregunta (acerca de un tutorial o algo relativamente simple) es lo suficientemente diferente de esa para que permanezca abierta, pero es posible que desee consultarla para obtener más consejos.
Llego tarde a la fiesta, pero recomendaría Game Scripting Mastery, que te ayuda a escribir un lenguaje de script funcional y su VM desde cero. Y con muy poco prerrequisito.
Otro recurso a considerar es la implementación del lenguaje Lua . Es una máquina virtual basada en el registro que tiene una buena reputación de rendimiento. El código fuente está en ANSI C89, y generalmente es muy legible.
Al igual que con la mayoría de los lenguajes de script de alto rendimiento, el usuario final ve un lenguaje dinámico de alto nivel legible (con funciones como cierres, llamadas de cola, cadenas inmutables, números y tablas hash como los tipos de datos principales, funciones como valores de primera clase y más) . El texto de origen se compila al código de bytes de la máquina virtual para su ejecución por una implementación de máquina virtual cuyo esquema es más o menos como lo describe la respuesta de Edmund .
Se ha realizado un gran esfuerzo para mantener la implementación de la VM tanto portátil como eficiente. Si se necesita aún más rendimiento, existe un compilador justo a tiempo desde el código de byte de VM a las instrucciones nativas para x86 de 32 bits, y está en versión beta para 64 bits.
Para comenzar (incluso si no es C , pero C ++ ) puede darle un vistazo a muParser .
Es un analizador de expresiones matemáticas que utiliza una máquina virtual simple para ejecutar operaciones. Creo que incluso tú necesitas tiempo para entender todo; De todos modos, este código es más simple que una máquina virtual completa capaz de ejecutar un programa completo real . (Por cierto, estoy diseñando una biblioteca similar en C #: se trata de sus primeras etapas, pero las próximas versiones permitirán la compilación en .NET / VM IL o tal vez una nueva máquina virtual simple como muParser ).
Otra cosa interesante es NekoVM (ejecuta archivos de bytecode NekoVM ). Es un proyecto de código abierto escrito en C y su lenguaje principal (.neko) está pensado para ser generado por la tecnología de compilador de fuente a fuente . En el espíritu del último tema, vea Haxe del mismo autor (fuente abierta también).
Supongo que desea una máquina virtual en lugar de un simple intérprete. Creo que son dos puntos en un continuo. Un intérprete trabaja en algo cercano a la representación original del programa. Una máquina virtual funciona con instrucciones más primitivas (y autocontenidas). Esto significa que necesita una etapa de compilación para traducir la una a la otra. No sé si desea trabajar en eso primero o si aún tiene en mente una sintaxis de entrada.
Para un lenguaje dinámico, desea un lugar que almacene datos (como pares clave / valor) y algunas operaciones que actúen en él. La máquina virtual mantiene la tienda. El programa que se ejecuta en él es una secuencia de instrucciones (incluido el flujo de control). Necesitas definir el conjunto de instrucciones. Yo sugeriría un conjunto simple para empezar, como:
- Operaciones aritméticas básicas, incluidas comparaciones aritméticas, acceso a la tienda
- flujo de control básico
- impresión incorporada
Es posible que desee utilizar un método de cálculo basado en la pila para la aritmética, como hacen muchas máquinas virtuales. Todavía no hay mucha dinámica en lo anterior. Para llegar a eso, queremos dos cosas: la capacidad de calcular los nombres de las variables en tiempo de ejecución (esto solo significa operaciones de cadena) y algún tratamiento del código como datos. Esto podría ser tan simple como permitir referencias de funciones.
Lo ideal sería que la entrada a la máquina virtual estuviera en el bytecode. Si aún no tienes un compilador, esto podría generarse desde un ensamblador básico (que podría ser parte de la VM).
La VM en sí consiste en el bucle:
1. Look at the bytecode instruction pointed to by the instruction pointer.
2. Execute the instruction:
* If it''s an arithmetic instruction, update the store accordingly.
* If it''s control flow, perform the test (if there is one) and set the instruction pointer.
* If it''s print, print a value from the store.
3. Advance the instruction pointer to the next instruction.
4. Repeat from 1.
Tratar con los nombres de las variables calculadas puede ser complicado: una instrucción debe especificar en qué variables están los nombres calculados. Esto se puede hacer permitiendo que las instrucciones se refieran a un conjunto de constantes de cadena proporcionadas en la entrada.
Un programa de ejemplo (en ensamblador y bytecode):
offset bytecode (hex) source
0 01 05 0E // LOAD 5, .x
3 01 03 10 // .l1: LOAD 3, .y
6 02 0E 10 0E // ADD .x, .y, .x
10 03 0E // PRINT .x
12 04 03 // GOTO .l1
14 78 00 // .x: "x"
16 79 00 // .y: "y"
Los códigos de instrucción implícitos son:
"LOAD x, k" (01 x k) Load single byte x as an integer into variable named by string constant at offset k.
"ADD k1, k2, k3" (02 v1 v2 v3) Add two variables named by string constants k1 and k2 and put the sum in variable named by string constant k3.
"PRINT k" (03 k) Print variable named by string constant k.
"GOTO a" (04 a) Go to offset given by byte a.
Necesita variantes para cuando las variables son nombradas por otras variables, etc. (y los niveles de direccionamiento indirecto son difíciles de razonar). El ensamblador ve los argumentos como "AGREGAR .x, .y, .x" y genera el código de byte correcto para agregar desde constantes de cadena (y variables no calculadas).