Implementieren Sie den Universal Machine-Emulator

13

Ziel ist es, ein vollständiges Programm zu schreiben, das die Universal Machine von ICFP 2006 mit dem kürzesten Code emuliert. Die Universalmaschine verfügt über einen sehr einfachen Befehlssatz, der hier erläutert wird . Der Emulator muss einen Dateinamen aus dem Befehlszeilenargument lesen und die Datei als Programm ausführen, sodass Ihre Sprache Befehlszeilenargumente und stdin / out in irgendeiner Weise unterstützen muss. Der Emulator muss die Sandmarkierung innerhalb einer angemessenen Zeit (nicht Jahrzehnten) abschließen . Hier ist eine kurze Erklärung des Befehlssatzes:

Die Maschine hat acht Register, die jeweils eine 32-Bit-Ganzzahl ohne Vorzeichen enthalten.
Die Maschine enthält einen indizierten Satz von Arrays von 32-Bit-Ganzzahlzellen ohne Vorzeichen.
Kurz gesagt, der Zuweisungsbefehl gibt eine undurchsichtige 32-Bit-Uint zurück, die das Handle für das erstellte Array ist, das eine statische Größe hat und 32-Bit-Uint-Elemente enthält.
Das 0. Array bezeichnet das Programm. Es wird beim Start aus einer Big-Endian-Datei geladen.
Es gibt auch einen Anweisungszeiger, der auf eine Zelle im Array 0 zeigt.
Bei jedem Schritt wird eine Anweisung aus der Zelle gelesen, auf die der Zeiger zeigt, und der Zeiger wird inkrementiert, bevor etwas getan wird.
Die 4 höchstwertigen Bits repräsentieren den Opcode.
Wenn der Opcode 13 ist, stellen die nächsten 3 Bits das Register dar, und die anderen 25 stellen die Nummer dar, die in das Register geschrieben wird.
Andernfalls repräsentieren die 9 niedrigstwertigen Bits drei Register, beispielsweise A, B und C, wobei C durch die 3 niedrigstwertigen Bits repräsentiert wird.
Dann passiert abhängig vom Opcode Folgendes:
0. A = B, außer 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. Der Emulator beendet
8. B = Zuweisen (C)
9. Freigeben (C)
10. Ein Zeichen von C an Standard ausgeben
11. Ein Zeichen eingeben von stdin nach c
12. Kopieren Sie das Array B in das Array 0 und setzen Sie den Zeiger auf C

Ich habe eine unnötig komplexe, aber total schnelle Implementierung (ab) mit x86_64-Jitted-Assembly geschrieben (der Spaß beginnt mit emit ()) , die Ihnen sicherlich helfen würde, wenn Sie einige Aspekte der Maschine falsch verstehen .

mniip
quelle
Sie müssen sich entscheiden, ob dies Code-Golf oder ein Beliebtheitswettbewerb sein soll. Sie sind exklusiv.
Howard
@ Howard Ich verstehe, danke
Mniip
Wenn ich mich nicht irre, wird die Maschine als Big Endian und nicht als Little Endian bezeichnet.
Hasturkun
@Hasturkun d'oh Ich vermassle das immer, ich denke immer wieder, dass Big Endian für "im größeren Byte enden" steht
am
1
@mniip Big Endian und Little Endian sind Begriffe, die von Gullivers Reisen entlehnt wurden. Die kleinen Leute von Lilliput waren im Krieg mit den kleinen Leuten von Blefuscu, weil die Lilliputianer "Big Endianer" waren, die glaubten, man solle zuerst das große Ende eines gekochten Eies essen, und die Blefuscaner glaubten das Gegenteil. Das Original von Gulliver's Travels war ein ernstzunehmender Roman von Jonathan Swift. Der Autor kommentierte die Dummheit, wegen politischer und religiöser Differenzen in den Krieg zu ziehen. Gulliver musste gehen, nachdem er wegen Hochverrats angeklagt worden war, dass er sich geweigert hatte, im Krieg mitzuhelfen.
Level River St

Antworten:

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

* Wieder überarbeitet *. Es ist so klein, wie ich es jetzt möglicherweise bekommen kann. Ich habe einige Variablen am anderen Ende des Alphabets beibehalten, damit die Regex, die die $ -Zeichen einfügt, die STDIN-Konstante nicht beeinträchtigt. Deshalb hier ein kleines Glossar:

  • U: Anweisungszeiger
  • V: Index des gerade getesteten Opcodes
  • W: aktuelles Anweisungswort
  • X: die 8 Universalregister
  • Y: Hauptspeicher (jeder Block ist 1-basiert, da auf diese Weise unpack()Arrays zurückgegeben werden)
  • Z: ID des nächsten freien Speicherblocks (wird eventuell überlaufen, aber der Sandmark verwendet nur ~ 92 Millionen)
  • A, B, C sind die Register des aktuellen Befehls wie in der Spezifikation

Eine Division ohne Vorzeichen ist ein subtiles Problem (dies *1ist erforderlich, um sicherzustellen, dass große Zahlen auf das richtige int zurückgesetzt werden), aber der Rest der Arithmetik lässt sich leicht auf 32 Bit beschränken, indem das Arithmetikregister A|=0nach jedem Befehl mit 0 ( ) ODER-verknüpft wird .


Ich fand dieses Projekt wirklich interessant, aber das Bestreben, die Anzahl der Zeichen zu minimieren, machte es langsam und unelegant. Daher erstellte ich auch eine einfache (nicht golfene) Java-Version, mit der der Sandmark in wenigen Minuten fertiggestellt werden kann, anstatt den ganzen Tag in Anspruch zu nehmen:

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
quelle
Ich glaube nicht, dass Sie das Ergebnis der Division auf 32 Bit anpassen müssen, da es immer kleiner oder gleich der Dividende ist, die bereits angepasst wurde
Mniip
Wie sieht es aus Neugier ungolfed aus?
Tim Seguine
@mniip Es ist jetzt etwas anders aufgebaut, aber ich muss vorsichtig mit der Teilung sein, da die Zahlen während der Teilung nicht signiert sind und in jedem anderen Moment unterschrieben werden.
Boann
3

Perl, 407

Es sieht so aus, als wäre die Frage zu komplex, eigentlich ist sie sehr einfach.
Ich bin immer noch sehr neu in Perl, hier ist es sowieso

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

Es läuft sehr langsam, wahrscheinlich 800x langsamer als das JITed x86_64.
Außerdem hat ein Freund von mir eine Referenz-C-Implementierung durchgeführt

mniip
quelle
Ist dies ein Problem im Referenz-C-Code ?: if(((Memory[++PC]>>28)&15) == 13) { Registers[(Memory[PC]>>25)&7] = (Memory[PC]&0x01ffffff);Der Befehl wird nicht zwischengespeichert, sodass Opcodes, die nicht 13 sind, den nächsten Befehl vorab ausführen würden, nicht wahr?
Luser Droog
2

C, 924 838 825 696 646 623

Ich speichere einen "Zeiger" (Byte-Offset) in dem bin der Anweisung angegebenen Register und verwende jedes Register, das ein Array im Pseudocode kennzeichnet, auf die gleiche Weise (oder umgekehrt, um einen Zeiger wiederherzustellen), um später auf dieses Array zuzugreifen. Muss noch das Testprogramm ausprobieren ...

Bearbeiten: Kommentare hinzugefügt.

Edit: feste Anweisung 12. Ändere den Zeiger, nicht die Anweisung im Speicher. Bei der Zählung werden alle Kommentare, Einrückungen und Zeilenumbrüche entfernt.

Bearbeiten: Es scheint jetzt ausgeführt zu werden, vorausgesetzt, ich interpretiere die Ergebnisse richtig. :) Die letzte Erkenntnis war, dass das Array 0 tatsächlich durch das Handle 0 referenziert wird , das sich in einem nicht initialisierten Register befindet. Eine sehr verdrehte kleine Maschine! :)

Bearbeiten: Debugging-Apparat neu geschrieben, um ihn zu verwenden, writeanstatt printf... Die Idee hier ist , Fehler zu entfernen . :) Edit: putchar() und getchar()sind auch keine mit sbrk. Es funktioniert jetzt und erscheint ziemlich schnell.

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

Nur für Little-Endian gibt es eine 611- Zeichen-Version.

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

Eingekerbt und kommentiert, mit (erweitertem) kommentiertem Debugging-Apparat.

//#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
quelle
Array-Handles sind zu 100% undurchsichtig. Unabhängig davon, was Sie übergeben, soll das Programm beim Zugriff auf Arrays denselben Wert verwenden. PS Ich habe gerade versucht, es zu kompilieren, Sie vermissen ein paar enthält. PPS hast du es jemals kompiliert? was ist lbreakund wie Sie unary- *einint
mniip
Ja. Ein bisschen zu eifrig. :) Der aktualisierte Code wird mit gcc unter Cygwin kompiliert.
Luser Droog
@mniip Also ist es nur das Array 0 , das mit "number" bezeichnet wird?
Luser Droog
kompiliert es einfach, es führt nur 2 Anweisungen aus Sandmark aus: d000108f c0000030und beendet dann
mniip
Ich habe einen Fehler behoben. Es führt jetzt 7 Befehle aus, bevor es anhält.
Luser Droog