A veces se me ocurre una idea para deshacerse de la cual es muy difícil. Esto me paso a mi.
Decidí crear una máquina virtual (VM), dado que en ese momento no tenía ideas, me pareció que era una gran idea. Si está interesado, ¡vaya al corte!
Teoría
Primero, una pequeña teoría. ¿Qué es una máquina virtual en general? Este es un programa o un conjunto de programas que le permite emular algún tipo de plataforma de hardware, en otras palabras, un emulador de computadora.
Las máquinas virtuales en sí mismas son diferentes, por ejemplo, Virtual Box es una máquina virtual clásica que le permite emular una computadora real, pero por ejemplo, JVM (máquina virtual Java) no puede hacer esto.
Mi versión de VM será algo similar a la JVM, simplemente porque es un proyecto de capacitación más que el objetivo de crear una VM poderosa.
El recuerdo
Entonces, ahora descubramos la memoria. Para crear memoria, decidí usar una matriz int sin firmar. El tamaño de la matriz se determina usando una macro, en mi versión el tamaño de la memoria es de 4096 bytes (hay 1024 elementos en la matriz, y dado que en la mayoría de las plataformas se asignan 4 bytes para datos int sin signo, luego 1024 * 4 = 4096), entre otras cosas, definiremos 8 registros por 8 celdas en cada uno ya serán 256 bytes (8 * 8 * 4 = 256). Se ve así:
#define MEMSIZE 1024 unsigned int memory[MEMSIZE]; unsigned int reg[8][8];
Programacion
Tenemos memoria, pero ¿ahora cómo escribir código para nuestra VM? Ahora trataremos este problema, para comenzar, determinaremos los comandos que ejecutará nuestra máquina:
enum commands { CRG = 1, CRC, PRG, PRC };
Cada equipo tiene su propia bandera que define algunos parámetros adicionales.
describiremos banderas:
enum flags { STDI = 1, STDA };
El comando estándar tiene la forma: [comando] [bandera] [datos] (la apariencia de algunos comandos puede diferir), en base a esto escribiremos un intérprete simple:
if (memory[cell] == CRG && memory[cell + 1] == STDI) { indxX = memory[cell + 2]; cell++; } else if (memory[cell] == CRC && memory[cell + 1] == STDI) { indxY = memory[cell + 2]; cell++; } else if (memory[cell] == PRG && memory[cell + 1] == STDI) { reg[indxX][0] = memory[cell + 2]; cell++; } else if (memory[cell] == PRC && memory[cell + 1] == STDI) { reg[indxX][indxY] = memory[cell + 2]; cell++; }
indxX e indxY son variables que almacenan la posición actual del cursor en el registro de registro.
La celda es una variable que almacena la posición actual del cursor en la matriz de memoria.
Pero programar con números no es muy conveniente, así que usando el preprocesador C describiremos nuestro ensamblador. Entiendo que escribir asm con macros no es muy bueno, pero esta solución es temporal.
Nuestro código asm se ve así:
#define $CRG {memory[memIndx++] = CRG;} #define $CRC {memory[memIndx++] = CRC;} #define $PRG {memory[memIndx++] = PRG;} #define $PRC {memory[memIndx++] = PRC;} #define _$STDI {memory[memIndx++] = STDI;} #define _$STDA {memory[memIndx++] = STDA;} #define _$DATA memory[memIndx++] =
memIndx es una variable que almacena la posición actual del cursor en la matriz de memoria.
Y aquí está el código en nuestro ASM que pone 123 en el registro en la dirección [1] [0] (primer registro, celda cero):
$CRG _$STDI _$DATA 1; $CRC _$STDI _$DATA 0; $PRC _$STDI _$DATA 123;
¡Felicitaciones, ahora tenemos una apariencia de asm para nuestro automóvil!
Lanzar programas
Logramos que nuestra máquina ejecute programas, pero el código carece de portabilidad de una máquina a otra, por lo que ahora crearemos un generador de código de máquina desde asm (y le recuerdo que, a diferencia de las computadoras reales, nuestra máquina tiene un código de máquina que no se presenta en forma binaria, y números decimales), en principio, no es tan difícil, pero primero, pensemos en la implementación.
Primero tenemos un código ASM, ahora necesitamos traducirlo a números, luego escribir el código de máquina resultante en un archivo .ncp (programa de código numérico, de hecho es un archivo de texto, pero para distinguirlo de todo lo demás, se me ocurrió mi propia extensión), después de eso necesitamos ejecutar el archivo .ncp, es fácil de hacer, ya que el intérprete que escribimos anteriormente reconoce los números; solo necesitamos extraer datos del archivo y convertirlos en números usando atoi ().
Pasemos de las palabras a los hechos:
Leer el código y escribirlo en un archivo:
if (memory[i] == CRG && memory[i + 1] == STDI) { fprintf(code, "%d %d ", CRG, STDI); i++; } else if (memory[i] == CRC && memory[i + 1] == STDI) { fprintf(code, "%d %d ", CRC, STDI); i++; } else if (memory[i] == PRG && memory[i + 1] == STDI) { fprintf(code, "%d %d ", PRG, STDI); i++; } else if (memory[i] == PRC && memory[i + 1] == STDI) { fprintf(code, "%d %d ", PRC, STDI); i++; }
El código es parte del cuerpo de la función ncpGen ().
Lectura de un archivo y su ejecución:
if (prog != NULL) { fread(txt, 1, len, prog); tok = strtok(txt, " "); while (tok != NULL) { memory[i] = atoi(tok); tok = strtok(NULL, " "); if (argc == 3 && strcmp(argv[2], "-m") == 0) { printf("%d\n", memory[i]); } i++; } memInter(); } else { perror("Fail"); }
Ahora, definamos una macro para que, en lugar de interpretar asm, el código se convierta en .ncp:
#define _toNCP(name) {strcpy(filename, name);} {ncpGen();}
En todo caso, ¡el artículo no presenta todo el código, sino solo una pequeña parte!
El código completo está en
el repositorio del proyecto.
Muchas gracias por leer!