Implementare l'emulatore Universal Machine


13

L'obiettivo è quello di scrivere un programma completo che emuli la macchina universale da ICFP 2006 con il codice più breve. La macchina universale ha un set di istruzioni molto semplice spiegato qui . L'emulatore deve leggere un nome file dall'argomento della riga di comando ed eseguire il file come programma, quindi la tua lingua deve supportare gli argomenti della riga di comando e stdin / out in qualche modo. L'emulatore deve completare il sandmark entro un tempo ragionevole (non decenni). Ecco una breve spiegazione del set di istruzioni:

La macchina ha otto registri ciascuno contenente un numero intero senza segno a 32 bit.
La macchina contiene un set indicizzato di matrici di celle intere senza segno a 32 bit.
In breve, l'istruzione di allocazione restituisce un uint opaco a 32 bit che è l'handle dell'array creato, che ha una dimensione statica e contiene elementi uint a 32 bit.
L'array 0 ° indica il programma. Viene caricato da un file big-endian all'avvio.
C'è anche un puntatore a istruzioni che punta a una cella nell'array 0.
Ad ogni passaggio, un'istruzione viene letta dalla cella a cui punta il puntatore e il puntatore viene incerementato prima di eseguire qualsiasi operazione.
I 4 bit più significativi rappresentano il codice operativo.
Se il codice operativo è 13, i successivi 3 bit rappresentano il registro e gli altri 25 rappresentano il numero che è scritto nel registro stesso.
Altrimenti i 9 bit meno significativi rappresentano tre registri, ad esempio A, B e C, dove C è rappresentato dai 3 bit meno significativi.
Quindi, a seconda del codice operativo, si verifica quanto segue:
0. A = B a meno che 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 & C)
7. L'emulatore esce
8. B = allocate (C)
9. deallocate (C)
10. emette un carattere da C a stdout
11. inserisci un carattere dallo stdin in C
12. copia l'array B nell'array 0 e imposta il puntatore su C

Ho scritto un'implementazione inutilmente complessa ma totalmente veloce (ab) usando l'assemblaggio j86 x86_64 (il divertimento inizia in emit ()) , che sicuramente ti aiuterebbe se fraintendi alcuni aspetti della Macchina.


Devi decidere se questo dovrebbe essere un code-golf o un concorso di popolarità. Sono esclusivi.
Howard,

@Howard vedo, grazie
mniip, il

Se non sbaglio, la macchina è descritta come Big Endian, non Little Endian.
Hasturkun,

@Hasturkun d'oh Ho sempre fatto un casino, continuo a pensare che Big Endian significhi "finire nel byte più grande"
mniip

1
@mniip Big Endian e Little Endian sono termini presi in prestito da Gulliver's Travels. La piccola gente di Lilliput era in guerra con la piccola gente di Blefuscu, perché i Lilliputians erano "Big Endian" che credevano che dovresti mangiare prima il grosso di un uovo sodo, e i Blefuscans credevano il contrario. L'originale Gulliver's Travels era un romanzo serio di Jonathan Swift. L'autore stava commentando la stupidità di andare in guerra per differenze politiche e religiose. Gulliver fu costretto a partire dopo essere stato accusato di tradimento per essersi rifiutato di dare una mano in guerra.
Level River St,

Risposte:


6

PHP: 443 416  384 byte

<?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",'])));

* Rinnovato di nuovo *. È il più piccolo possibile che riesca a ottenerlo ora. Ho tenuto alcune variabili in fondo all'estremità dell'alfabeto in modo che la regex che inserisce i segni $ non rovini la costante STDIN, quindi ecco un piccolo glossario:

  • U: puntatore alle istruzioni
  • V: indice del codice operativo attualmente in fase di test
  • W: parola di istruzione corrente
  • X: gli 8 registri di uso generale
  • Y: memoria principale (ogni blocco è basato su 1, poiché è così che unpack()restituisce le matrici)
  • Z: id del prossimo blocco di memoria libero (eventualmente overflow, ma il sandmark usa solo ~ 92 milioni)
  • A, B, C sono i registri dell'istruzione corrente come nelle specifiche

La divisione non firmata è un fastidio sottile (il *1 è necessario per garantire che grandi numeri vengano riportati all'int corretto), ma il resto dell'aritmetica è facile da mantenere a 32 bit ORingando il registro aritmetico con 0 ( A|=0) dopo ogni istruzione.


Ho trovato questo progetto davvero interessante, ma cercare di minimizzare il conteggio dei personaggi lo ha reso lento e inelegante, quindi ho anche creato una versione Java semplice (non giocata a golf), che può completare il segno di sabbia in pochi minuti invece di impiegare tutto il giorno:

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;
                }
            }
        }
    }
}

non penso che sia necessario regolare il risultato della divisione a 32 bit perché è sempre minore o uguale al dividendo, che è già regolato
mniip

Solo per curiosità, che aspetto ha un golf?
Tim Seguine,

@mniip Ora è un po 'diverso, ma devo stare attento con la divisione perché durante la divisione i numeri non sono firmati e in ogni altro momento sono firmati.
Boann,

3

Perl, 407

Sembra che la domanda possa sembrare troppo complessa, in realtà è molto semplice.
Sono ancora molto nuovo per Perl, comunque eccolo qui

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].";";}

Funziona molto lentamente, probabilmente 800 volte più lentamente di quello x86_64 JIT.
Inoltre, un mio amico ha fatto un'implementazione di riferimento C.


È un problema nel codice C di riferimento ?: if(((Memory[++PC]>>28)&15) == 13) { Registers[(Memory[PC]>>25)&7] = (Memory[PC]&0x01ffffff);l'istruzione non è memorizzata nella cache, quindi qualsiasi codice operativo non 13 eseguirà la pre-esecuzione dell'istruzione successiva, no?
Luser droog

2

C, 924 838 825 696 646 623

Memorizzo un "puntatore" (byte-offset) nel registro designato bnell'istruzione, e uso qualunque registro designi un array nello pseudocodice allo stesso modo (o viceversa, piuttosto, per ricostituire un puntatore) per accedere a tale array in un secondo momento. Devo ancora provare il programma di test ...

Modifica: aggiunti commenti.

Modifica: istruzione fissa 12. cambia il puntatore, non l'istruzione in memoria. Il conteggio viene rimosso con tutti i commenti, i rientri e le nuove righe.

Modifica: sembra essere in esecuzione ora, supponendo che io stia interpretando correttamente i risultati. :) La realizzazione finale è stata che l' array 0 è effettivamente referenziato dall'handle 0, che può essere trovato in un registro non inizializzato. Una piccola macchina molto contorta! :)

Modifica: riscrivi l'apparato di debug da utilizzare writeanziché printf... L'idea qui è quella di rimuovere i bug. :) Modifica: putchar() e getchar()sono anche no-no con sbrk. Ora funziona e sembra abbastanza veloce.

#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 per little-endian, c'è una versione di 611 caratteri.

#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;}}}

Rientrato e commentato, con apparato di debug (esteso) commentato.

//#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
        }
    }
}

Le maniglie di array sono opache al 100%. Indipendentemente da cosa lo passi, il programma dovrebbe usare lo stesso valore quando accedi agli array. PS Ho appena provato a compilarlo, ti manca un paio include. PPS l'hai mai compilato? cosa lbreake come puoi unario- *unint
mniip il

Sì. Un po 'troppo impaziente. :) Il codice aggiornato viene compilato con gcc su Cygwin.
Luser droog

@mniip Quindi è solo l' array 0 che è designato da "numero"?
Luser droog

appena compilato, esegue solo 2 istruzioni fuori dal sandmark: d000108f c0000030e quindi esce
mniip

Ho corretto un bug. Esegue 7 istruzioni ora prima di arrestarsi.
Luser droog
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.