Implémenter l'émulateur Universal Machine

13

L'objectif est d'écrire un programme complet qui émule la machine universelle d'ICFP 2006 avec le code le plus court. La machine universelle a un jeu d'instructions très simple expliqué ici . L'émulateur doit lire un nom de fichier à partir de l'argument de ligne de commande et exécuter le fichier en tant que programme, de sorte que votre langue doit prendre en charge les arguments de ligne de commande et stdin / out d'une manière ou d'une autre. L'émulateur doit terminer le point de repère dans un délai raisonnable (pas des décennies). Voici une courte explication du jeu d'instructions:

La machine possède huit registres contenant chacun un entier non signé 32 bits.
La machine contient un ensemble indexé de tableaux de cellules entières non signées 32 bits.
En bref, l'instruction d'allocation renvoie une uint 32 bits opaque qui est le descripteur du tableau créé, qui a une taille statique et contient des éléments uint 32 bits.
Le 0ème tableau indique le programme. Il est chargé à partir d'un fichier big-endian au démarrage.
Il existe également un pointeur d'instructions qui pointe vers une cellule du tableau 0.
À chaque étape, une instruction est lue dans la cellule vers laquelle pointe le pointeur et le pointeur est inceréré avant que quoi que ce soit soit fait.
Les 4 bits les plus significatifs représentent l'opcode.
Si l'opcode est 13, alors les 3 bits suivants représentent le registre, et les 25 autres représentent le nombre qui est écrit dans ledit registre.
Sinon, les 9 bits les moins significatifs représentent trois registres, disons A, B et C, où C est représenté par les 3 bits les moins significatifs.
Ensuite, en fonction de l'opcode, les événements suivants se produisent:
0. A = B sauf 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'émulateur se termine
8. B = allouer (C)
9. désallouer (C)
10. sortir un caractère de C vers stdout
11. entrer un caractère de stdin en C
12. copiez le tableau B dans le tableau 0 et réglez le pointeur sur C

J'ai écrit une implémentation (ab) inutilement complexe mais totalement rapide en utilisant l'assemblage jitted x86_64 (le plaisir commence dans emit ()) , ce qui vous aiderait à coup sûr si vous comprenez mal certains aspects de la machine.

mniip
la source
Vous devez décider si cela doit être un code-golf ou un concours de popularité. Ils sont exclusifs.
Howard
@Howard je vois, merci
mniip
Si je ne me trompe pas, la machine est décrite comme étant Big Endian, pas Little Endian.
Hasturkun du
@Hasturkun d'oh Je les gâche toujours, je continue de penser que Big Endian signifie "se terminant dans le plus gros octet"
mniip
1
@mniip Big Endian et Little Endian sont des termes empruntés à Gulliver's Travels. Le petit peuple de Lilliput était en guerre avec le petit peuple de Blefuscu, parce que les Lilliputiens étaient des "Big Endians" qui croyaient que vous devriez manger le gros bout d'un œuf à la coque en premier, et les Blefuscans croyaient l'inverse. Les voyages originaux de Gulliver étaient un roman sérieux de Jonathan Swift. L'auteur commentait la stupidité de partir en guerre pour des divergences politiques et religieuses. Gulliver a été contraint de partir après avoir été accusé de trahison pour avoir refusé d'aider à la guerre.
Level River St

Réponses:

6

PHP: 443 416  384 octets

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

* Remanié à nouveau *. Il est aussi petit que possible. J'ai gardé quelques variables à l'extrémité de l'alphabet afin que l'expression régulière qui insère les signes $ ne modifie pas la constante STDIN, alors voici un petit glossaire:

  • U: pointeur d'instruction
  • V: index de l'opcode en cours de test
  • W: mot d'instruction courant
  • X: les 8 registres à usage général
  • Y: mémoire principale (chaque bloc est basé sur 1, car c'est ainsi que les unpack()tableaux sont renvoyés)
  • Z: id du prochain bloc de mémoire libre (finira par déborder, mais le sandmark n'utilise que ~ 92 millions)
  • A, B, C sont les registres de l'instruction courante comme dans la spécification

La division non signée est une nuisance subtile (elle *1est nécessaire pour garantir que les grands nombres sont restitués au bon int), mais le reste de l'arithmétique est facile à conserver sur 32 bits en effectuant une opération OU sur le registre arithmétique avec 0 ( A|=0) après chaque instruction.


J'ai trouvé ce projet vraiment intéressant mais en s'efforçant de minimiser le nombre de caractères, il était lent et inélégant, j'ai donc également fait une version Java simple (non jouée au golf), qui peut compléter le point de repère en quelques minutes au lieu de prendre toute la journée:

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;
                }
            }
        }
    }
}
Boann
la source
Je ne pense pas que vous ayez besoin d'ajuster le résultat de la division à 32 bits, car il est toujours inférieur ou égal au dividende, qui est déjà ajusté
mniip
Par curiosité, à quoi cela ressemble-t-il sans golf?
Tim Seguine
@mniip La présentation est un peu différente, mais je dois faire attention à la division, car pendant la division, les chiffres ne sont pas signés et à tout autre moment, ils sont signés.
Boann
3

Perl, 407

Il semble que la question puisse sembler trop complexe, en fait c'est très simple.
Je suis encore très nouveau pour Perl, de toute façon ici c'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].";";}

Il fonctionne très lentement, probablement 800 fois plus lentement que celui JITed x86_64.
En outre, un de mes amis a fait une implémentation de référence C

mniip
la source
Est-ce un problème dans le code C de référence?: if(((Memory[++PC]>>28)&15) == 13) { Registers[(Memory[PC]>>25)&7] = (Memory[PC]&0x01ffffff);L'instruction n'est pas mise en cache, donc tout opcode non 13 pré-exécutera l'instruction suivante, non?
luser droog
2

C, 924 838 825 696 646 623

Je stocke un "pointeur" (décalage d'octet) dans le registre désigné bdans l'instruction, et j'utilise tout registre qui désigne un tableau dans le pseudocode de la même manière (ou inversement, plutôt, pour reconstituer un pointeur) pour accéder à ce tableau plus tard. Encore faut-il essayer le programme de test ...

Modifier: ajout de commentaires.

Edit: instruction fixe 12. changer le pointeur, pas l'instruction en mémoire. Le décompte est supprimé de tous les commentaires, retraits et nouvelles lignes.

Edit: Il semble fonctionner maintenant, en supposant que j'interprète correctement les résultats. :) La réalisation finale a été que le tableau 0 est en effet référencé par le handle 0, qui peut être trouvé dans un registre non initialisé. Une petite machine très tordue! :)

Edit: réécrit l'appareil de débogage à utiliser writeau lieu de printf.... L'idée ici est de supprimer les bugs. :) Edit: putchar() et getchar()sont également no-nos avec sbrk. Cela fonctionne maintenant et apparaît assez rapidement.

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

Pour le petit endian uniquement, il existe une version à 611 caractères.

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

Indenté et commenté, avec un appareil de débogage commenté (étendu).

//#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
        }
    }
}
luser droog
la source
Les poignées du tableau sont 100% opaques. Peu importe ce que vous lui transmettez, le programme est censé utiliser la même valeur lors de l'accès aux tableaux. PS J'ai juste essayé de le compiler, il vous manque quelques inclusions. PPS l'avez-vous déjà compilé? qu'est-ce que lbreaket comment pouvez-vous unary- *anint
mniip
Oui. Un peu trop pressé. :) Le code mis à jour se compile avec gcc sur Cygwin.
luser droog
@mniip Donc, c'est seulement le tableau 0 qui est désigné par "nombre"?
luser droog
vient de le compiler, il n'exécute que 2 instructions hors de sandmark: d000108f c0000030puis se
termine
J'ai corrigé un bug. Il exécute maintenant 7 instructions avant de s'arrêter.
luser droog