Implemente el emulador Universal Machine


13

El objetivo es escribir un programa completo que emule la Máquina Universal de ICFP 2006 con el código más corto. La máquina universal tiene un conjunto de instrucciones muy simple explicado aquí . El emulador tiene que leer un nombre de archivo del argumento de la línea de comandos y ejecutar el archivo como el programa, por lo que su idioma debe admitir los argumentos de la línea de comandos y stdin / out de alguna manera. El emulador tiene que completar la marca de arena dentro de un tiempo razonable (no décadas). Aquí hay una breve explicación del conjunto de instrucciones:

La máquina tiene ocho registros, cada uno con un entero sin signo de 32 bits.
La máquina contiene un conjunto indexado de matrices de celdas enteras sin signo de 32 bits.
En pocas palabras, la instrucción de asignación devuelve un uint opaco de 32 bits que es el identificador de la matriz creada, que tiene un tamaño estático, y contiene elementos uint de 32 bits.
La matriz 0 'denota el programa. Se carga desde un archivo big-endian en el inicio.
También hay un puntero de instrucción que apunta a una celda en la matriz 0.
En cada paso, se lee una instrucción de la celda a la que apunta el puntero, y el puntero se incementa antes de que se haga algo.
Los 4 bits más significativos representan el código de operación.
Si el código de operación es 13, entonces los siguientes 3 bits representan el registro, y los otros 25 representan el número que está escrito en dicho registro.
De lo contrario, los 9 bits menos significativos representan tres registros, por ejemplo, A, B y C, donde C está representado por los 3 bits menos significativos.
Luego, dependiendo del código de operación, sucede lo siguiente:
0. A = B a menos que C == 0
1. A = B [C]
2. A [B] = C
3. A = B + C
4. A = B * C
5. A = B / C
6. A = ~ (B y C)
7. El emulador sale
8. B = asignar (C)
9. desasignar (C)
10. generar un carácter de C a stdout
11. ingresar un carácter de stdin a C
12. copie la matriz B en la matriz 0 y configure el puntero en C

He escrito una implementación (ab) innecesariamente compleja pero totalmente rápida usando el ensamblaje jitted x86_64 (la diversión comienza en emit ()) , lo que seguramente te ayudará si no entiendes algunos de los aspectos de la Máquina.


Tienes que decidir si esto debería ser código-golf o concurso de popularidad. Son exclusivos.
Howard

@Howard Ya veo, gracias
mniip

Si no me equivoco, la máquina se describe como Big Endian, no Little Endian.
Hasturkun

@Hasturkun d'oh Siempre me equivoco, sigo pensando que Big Endian significa "terminar en el byte más grande"
mniip

1
@mniip Big Endian y Little Endian son términos tomados de Gulliver's Travels. La gente pequeña de Liliput estaba en guerra con la gente pequeña de Blefuscu, porque los Liliputienses eran "Big Endians" que creían que primero debías comer el extremo grande de un huevo hervido, y los Blefuscans creían lo contrario. Los viajes de Gulliver originales fueron una novela seria de Jonathan Swift. El autor comentaba la estupidez de ir a la guerra por diferencias políticas y religiosas. Gulliver se vio obligado a irse después de ser acusado de traición por negarse a ayudar en la guerra.
Level River St

Respuestas:


6

PHP: 443 416  384 bytes

<?php @eval(ereg_replace('[U-Z]','$\0',strtr('for(Y=[unpack("N*",join(file($argv[1])))];;A|=0){{W=Y[V=0][++U]
C&&A=B
A=Y[B][C+1]
Y[A][B+1]=C
A=B+C
A=B*C
A=bcdiv(PB),PC))*1
A=~B|~C
die
B=++Z
unset(Y[C])
echo chr(C)
C=fgetc(STDIN);C=ord(C)-(C=="")
Y[0]=Y[B|0];U=C
X[W>>25&7]=W&33554431;}}',['
'=>';}if((W>>28&15)==V++){',A=>'X[W>>6&7]',B=>'X[W>>3&7]',C=>'X[W&7]',P=>'sprintf("%u",'])));

* Renovado de nuevo *. Es lo más pequeño que puedo obtener ahora. Mantuve algunas variables en el extremo más alejado del alfabeto para que la expresión regular que inserta los signos $ no altere la constante STDIN, así que aquí hay un pequeño glosario:

  • U: puntero de instrucciones
  • V: índice de código de operación que se está probando actualmente
  • W: palabra de instrucción actual
  • X: los 8 registros de propósito general
  • Y: memoria principal (cada bloque está basado en 1, ya que así es como unpack() devuelve las matrices)
  • Z: id del siguiente bloque de memoria libre (eventualmente se desbordará, pero la marca de arena solo usa ~ 92 millones)
  • A, B, C son los registros de la instrucción actual como en la especificación

La división sin signo es una molestia sutil ( *1es necesaria para garantizar que los números grandes vuelvan al int correcto) pero el resto de la aritmética es fácil de mantener 32 bits al OR el registro aritmético con 0 ( A|=0) después de cada instrucción.


Encontré este proyecto realmente interesante, pero el esfuerzo por minimizar el recuento de caracteres lo hizo lento y poco elegante, por lo que también hice una versión Java simple (no golfizada), que puede completar la marca de arena en unos minutos en lugar de tomar todo el día:

import java.io.*;
import java.util.HashMap;

public class UniversalMachine {
    public static void main(String[] args) throws IOException {
        if (args.length == 0) {
            System.err.println("Program not specified.");
            System.exit(1);
        }

        int[] program;
        try (RandomAccessFile raf = new RandomAccessFile(args[0], "r")) {
            program = new int[(int)(raf.length() / 4)];
            for (int i = 0; i < program.length; i++) {
                program[i] = raf.readInt();
            }
        }

        HashMap<Integer,int[]> memory = new HashMap<>();
        memory.put(0, program);
        int nextMemKey = 1;

        int[] R = new int[8]; // Registers
        int IP = 0; // Execution Finger (Instruction Pointer)

        loop: for (;;) {
            int ins = program[IP++];
            int op = ins >>> 28;
            if (op == 13) { // Orthography
                int A = (ins >> 25) & 7;
                int num = ins & 0x01FF_FFFF;
                R[A] = num;
            } else {
                final int A = (ins >> 6) & 7;
                final int B = (ins >> 3) & 7;
                final int C = (ins >> 0) & 7;
                switch (op) {
                case 0: // Conditional Move
                    if (R[C] != 0) R[A] = R[B];
                    break;
                case 1: // Array Index
                    R[A] = memory.get(R[B])[R[C]];
                    break;
                case 2: // Array Amendment
                    memory.get(R[A])[R[B]] = R[C];
                    break;
                case 3: // Addition
                    R[A] = R[B] + R[C];
                    break;
                case 4: // Multiplication
                    R[A] = R[B] * R[C];
                    break;
                case 5: // Division
                    R[A] = (int)((R[B] & 0xFFFF_FFFFL) / (R[C] & 0xFFFF_FFFFL));
                    break;
                case 6: // Not-And
                    R[A] = ~(R[B] & R[C]);
                    break;
                case 7: // Halt
                    break loop;
                case 8: // Allocation
                    // note: must use C before setting B, as they may be the same reg
                    memory.put(nextMemKey, new int[R[C]]);
                    R[B] = nextMemKey++;
                    break;
                case 9: // Abandonment
                    memory.remove(R[C]);
                    break;
                case 10: // Output
                    System.out.print((char)R[C]);
                    break;
                case 11: // Input
                    R[C] = System.in.read();
                    break;
                case 12: // Load Program
                    IP = R[C];
                    if (R[B] != 0) {
                        memory.put(0, program = memory.get(R[B]).clone());
                    }
                    break;
                }
            }
        }
    }
}

no creo que necesite ajustar el resultado de la división a 32 bits porque siempre es menor que o igual al dividendo, que ya está ajustado
mniip

Solo por curiosidad, ¿cómo se ve sin golf?
Tim Seguine

@mniip Ahora está un poco diferente, pero tengo que tener cuidado con la división porque durante la división los números no están firmados, y en cualquier otro momento están firmados.
Boann

3

Perl, 407

Parece que la pregunta puede parecer demasiado compleja, en realidad es muy simple.
Sin embargo, todavía soy muy nuevo en Perl, de todos modos aquí está

open$f,shift;binmode$f;push@{$m[0]},unpack'N',$b while read$f,$b,4;$z=2**32;while(){$o=$m[0][$p++];$a=\$r[$o>>6&7];$b=\$r[$o>>3&7];$c=\$r[$o&7];eval qw,$$a=($$b)if$$c $$a=$m[$$b][$$c] $m[$$a][$$b]=$$c $$a=($$b+$$c)%$z $$a=$$b*$$c%$z $$a=$==$$b/$$c $$a=$$b&$$c^($z-1) exit $$b=scalar@m;$m[$$b]=[] undef$m[$$c] print(chr$$c) $$c=ord(getc) $m[0]=[@{$m[$$b]}]if$$b;$p=$$c $r[$o>>25&7]=$o&33554431,[$o>>28].";";}

Funciona muy lento, probablemente 800 veces más lento que el JITed x86_64.
Además, un amigo mío hizo una implementación de referencia C


¿Es este un problema en el código C de referencia if(((Memory[++PC]>>28)&15) == 13) { Registers[(Memory[PC]>>25)&7] = (Memory[PC]&0x01ffffff);?
luser droog

2

C, 924 838 825 696 646 623

Almaceno un "puntero" (desplazamiento de byte) en el registro designado ben la instrucción, y uso cualquier registro que designe una matriz en el pseudocódigo de la misma manera (o inversa, más bien, para reconstituir un puntero) para acceder a esa matriz más tarde. Todavía necesito probar el programa de prueba ...

Editar: comentarios agregados.

Edición: instrucción fija 12. cambie el puntero, no la instrucción en la memoria. El recuento se elimina con todos los comentarios, sangrías y líneas nuevas.

Editar: parece estar ejecutándose ahora, suponiendo que estoy interpretando los resultados correctamente. :) La conclusión final fue que la matriz 0 de hecho está referenciada por el identificador 0, que se puede encontrar en un registro no inicializado. ¡Una pequeña máquina muy retorcida! :)

Editar: reescribió el aparato de depuración para usar en writelugar de printf... La idea aquí es eliminar los errores. :) Editar: putchar() y getchar()también son no-nos con sbrk. Ahora funciona y parece bastante rápido.

#define O(_)*a=*b _*c;B
#define B break;case
#define U unsigned
U*m,r[8],*p,*z,f,x,*a,*b,*c;main(int n,char**v){U char
u[4];z=m=p=sbrk(4);f=n>1?open(v[1],0):0;\
while(read(f,u,4)){*m++=(((((*u<<8)|u[1])<<8)|u[2])<<8)|u[3];sbrk(4);}sbrk(4);\
for(;x=*p++,1;){c=r+(x&7);b=r+((x>>3)&7);a=r+((x>>6)&7);switch(x>>28){case
0:*c?*a=*b:0;B
1:*a=(*b?m+*b:z)[*c];B
2:(*a?m+*a:z)[*b]=*c;B
3:O(+)4:O(*)5:O(/)6:*a=~(*b&*c);B
7:return 0;case
8:*b=1+(U*)sbrk(4*(1+*c))-m;(m+*b)[-1]=*c;B
9:B
10:*u=*c;write(1,u,1);B 
11:read(0,u,1);*c=*u;B
12:*b?memcpy(z=sbrk(4*(m+*b)[-1]),m+*b,4*(m+*b)[-1]):0;p=&z[*c];B
13:a=r+((x>>25)&7);*a=x&0x1ffffff;}}}

Solo para little-endian, hay una versión de 611 caracteres.

#define O(_)*a=*b _*c;B
#define B break;case
#define U unsigned
U*m,r[8],*p,*z,f,x,*a,*b,*c;main(int n,char**v){U char
u[4];z=m=p=sbrk(4);f=n>1?open(v[1],0):0;while(read(f,u,4)){*m++=(((((*u<<8)|u[1])<<8)|u[2])<<8)|u[3];sbrk(4);}sbrk(4);for(;x=*p++,1;){c=r+(x&7);b=r+((x>>3)&7);a=r+((x>>6)&7);switch(x>>28){case
0:*c?*a=*b:0;B
1:*a=(*b?m+*b:z)[*c];B
2:(*a?m+*a:z)[*b]=*c;B
3:O(+)4:O(*)5:O(/)6:*a=~(*b&*c);B
7:return 0;case
8:*b=1+(U*)sbrk(4*(1+*c))-m;(m+*b)[-1]=*c;B
9:B
//10:*u=*c;write(1,u,1);B //generic
10:write(1,c,1);B //little-endian
//11:read(0,u,1);*c=*u;B //generic
11:read(0,c,1);B //little-endian
12:*b?memcpy(z=sbrk(4*(m+*b)[-1]),m+*b,4*(m+*b)[-1]):0;p=&z[*c];B
13:a=r+((x>>25)&7);*a=x&0x1ffffff;}}}

Sangrado y comentarios, con aparato de depuración comentado (extendido).

//#define DEBUG 1
#include <fcntl.h> // open
#include <signal.h> // signal
#include <stdio.h> // putchar getchar
#include <string.h> // memcpy
#include <sys/types.h> // open
#include <sys/stat.h> // open
#include <unistd.h> // sbrk read
unsigned long r[8],*m,*p,*z,f,x,o,*a,*b,*c; // registers memory pointer zero file working opcode A B C
char alpha[] = "0123456789ABCDEF";
//void S(int x){signal(SIGSEGV,S);sbrk(9);} // autogrow memory while reading program
void writeword(int fd, unsigned long word){
    char buf[8];
    unsigned long m=0xF0000000;
    int off;
    for (off = 28; off >= 0; m>>=4, off-=4) {
        buf[7-(off/4)]=alpha[(word&m)>>off];
    }
    write(fd, buf, 8);
    write(fd, " ", 1);
}
int main(int n,char**v){
#ifdef DEBUG
    int fdlog;
#endif
    unsigned char u[4]; // 4-byte buffer for reading big-endian 32bit words portably
    int cnt;

#ifdef DEBUG
    fdlog = open("sandlog",O_WRONLY|O_CREAT|O_TRUNC, 0777);
#endif
    z=m=p=sbrk(4); // initialize memory and pointer
    //signal(SIGSEGV,S); // invoke autogrowing memory -- no longer needed
    f=n>1?open(v[1],O_RDONLY):0; // open program
    while(read(f,u,4)){ // read 4 bytes
        *m++=(((((*u<<8)|u[1])<<8)|u[2])<<8)|u[3]; // pack 4 bytes into 32bit unsigned in mem
        sbrk(4); // don't snip the end of the program
    }
    sbrk(4);
    for(cnt=0;x=*p++,1;cnt++){ // working = *ptr; ptr+=1
        c=r+(x&7); // interpret C register field
        b=r+((x>>3)&7); // interpret B register field
        a=r+((x>>6)&7); // interpret A register field
#ifdef DEBUG
        {int i;write(fdlog,"{",1);for(i=0;i<8;i++)writeword(fdlog, r[i]);
            write(fdlog,"} ",2);
        }
        write(fdlog, alpha+(x), 1);
        write(fdlog, alpha+(x>>28), 1);
#endif
        switch(o=x>>28){ // interpret opcode
            case 0:
#ifdef DEBUG
                write(fdlog, "if(rX)rX=rX\n", 12);
#endif
                *c?*a=*b:0;
                break; // Conditional Move A=B unless C==0
            case 1:
#ifdef DEBUG
                write(fdlog, "rX=rX[rX]\n", 10);
#endif
                *a=(*b?m+*b:z)[*c];
                break; // Array Index A=B[C]
            case 2:
#ifdef DEBUG
                write(fdlog, "rX[rX]=rX\n", 10);
#endif
                (*a?m+*a:z)[*b]=*c;
                break; // Array Amendment A[B] = C
            case 3:
#ifdef DEBUG
                write(fdlog, "rX=rX+rX\n", 9);
#endif
                *a=*b+*c;
                break; // Addition A = B + C
            case 4:
#ifdef DEBUG
                write(fdlog, "rX=rX*rX\n", 9);
#endif
                *a=*b**c;
                break; // Multiplication A = B * C
            case 5:
#ifdef DEBUG
                write(fdlog, "rX=rX/rX\n", 9);
#endif
                *a=*b/ *c;
                break; // Division A = B / C
            case 6:
#ifdef DEBUG
                write(fdlog, "rX=~(rX&rX)\n", 12);
#endif
                *a=~(*b&*c);
                break; // Not-And A = ~(B & C)
            case 7:
#ifdef DEBUG
                write(fdlog, "halt\n", 5);
#endif
                return 0; // Halt 
            case 8:
#ifdef DEBUG
                write(fdlog, "rX=alloc(rX)\n", 13);
#endif
                *b=1+(unsigned long*)sbrk(4*(1+*c))-m;
                   (m+*b)[-1]=*c;

                   break; // Allocation B = allocate(C)
            case 9:
#ifdef DEBUG
                   write(fdlog, "free(rX)\n", 9);
#endif
                   break; // Abandonment deallocate(C)
            case 10:
#ifdef DEBUG
                   write(fdlog, "output(rX)\n", 11);
#endif
                   //putchar(*c);
                   //*u=u[1]=u[2]=' ';
                   u[3]=(char)*c;
                   write(fileno(stdout), u+3, 1);
                   break; // Output char from C to stdout
            case 11:
#ifdef DEBUG
                   write(fdlog, "rX=input()\n", 11);
#endif
                   //x=getchar();*c=x;
                   read(fileno(stdin), u+3, 1);
                   *c=u[3];
                   break; // Input char from stdin into C
            case 12:
#ifdef DEBUG
                   write(fdlog, "load(rX)[rX]\n", 13);
#endif
                    *b?memcpy(z=sbrk(4*(m+*b)[-1]),m+*b,4*(m+*b)[-1]):0;
                    p=&z[*c];
                    break; // Load Program copy the array B into the 0 array, Ptr=C
            case 13:
#ifdef DEBUG
                    write(fdlog, "rX=X\n", 5);
#endif
                    a=r+((x>>25)&7);*a=x&0x1ffffff; // Orthography REG=immediate-25bit
        }
    }
}

Los mangos de la matriz son 100% opacos. No importa lo que pase, se supone que el programa debe usar el mismo valor al acceder a las matrices. PD: acabo de intentar compilarlo, te faltan un par incluye. PPS ¿alguna vez lo compiló? ¿Qué lbreaky cómo puedes unary- *anint
mniip

Si. Un poco demasiado ansioso. :) El código actualizado se compila con gcc en Cygwin.
luser droog

@mniip ¿Entonces solo la matriz 0 está designada por "número"?
luser droog

solo lo compiló, solo ejecuta 2 instrucciones fuera de la marca de arena: d000108f c0000030y luego sale
mniip

Arreglé un error. Ejecuta 7 instrucciones ahora antes de detenerse.
luser droog
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.