Implementar o emulador da Universal Machine

13

O objetivo é escrever um programa completo que emule a Máquina Universal do ICFP 2006 com o código mais curto. A Universal Machine possui um conjunto de instruções muito simples, explicado aqui . O emulador precisa ler um nome de arquivo no argumento da linha de comando e executar o arquivo como o programa, para que seu idioma tenha suporte para argumentos da linha de comando e stdin / out de alguma forma. O emulador precisa concluir a marca de areia dentro de um tempo razoável (não décadas). Aqui está uma breve explicação do conjunto de instruções:

A máquina possui oito registros, cada um contendo um número inteiro não assinado de 32 bits.
A máquina contém um conjunto indexado de matrizes de células inteiras não assinadas de 32 bits.
Em poucas palavras, a instrução de alocação retorna um uint opaco de 32 bits, que é o identificador da matriz criada, que tem um tamanho estático e contém elementos uint de 32 bits.
A matriz 0 indica o programa. Ele é carregado de um arquivo big-endian na inicialização.
Há também um ponteiro de instrução que aponta para uma célula na matriz 0.
Em cada etapa, uma instrução é lida na célula para a qual o ponteiro aponta e o ponteiro é inserido antes de qualquer coisa.
Os 4 bits mais significativos representam o código de operação.
Se o opcode for 13, os próximos 3 bits representam o registro e os outros 25 representam o número que está escrito no referido registro.
Caso contrário, os 9 bits menos significativos representam três registros, digamos, A, B e C, onde C é representado pelos 3 bits menos significativos.
Então, dependendo do código de operação, acontece o seguinte:
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 & C)
7. O emulador sai
8. B = aloca (C)
9. desaloca (C)
10. gera um caractere de C para stdout
11. insere um caractere de stdin para C
12. copie a matriz B para a matriz 0 e defina o ponteiro para C

Eu escrevi uma implementação (ab) desnecessariamente complexa, mas totalmente rápida, usando o assembly jitted x86_64 (a diversão começa em emit ()) , que com certeza o ajudaria se você não entender alguns aspectos da máquina.

mniip
fonte
Você precisa decidir se isso deve ser um código de golfe ou um concurso de popularidade. Eles são exclusivos.
28513 Howard Howard
@ Howard I see, thanks #
mniip
Se não me engano, a máquina é descrita como sendo Big Endian, não Little Endian.
precisa saber é o seguinte
@Hasturkun d'oh eu sempre bagunça estes acima, eu continuo pensando Big Endian significa "terminando no byte maior"
mniip
1
@mniip Big Endian e Little Endian são termos emprestados das Viagens de Gulliver. O povo pequeno de Lilliput estava em guerra com o povo pequeno de Blefuscu, porque os liliputianos eram "big endians" que acreditavam que você deveria comer primeiro a maior parte de um ovo cozido, e os blefuscanos acreditavam no contrário. As Viagens de Gulliver originais foram um romance sério de Jonathan Swift. O autor comentava a estupidez de ir à guerra por diferenças políticas e religiosas. Gulliver foi forçado a sair depois de ser acusado de traição por se recusar a ajudar na guerra.
Level River St

Respostas:

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 novamente *. É tão pequeno quanto eu posso conseguir agora. Eu mantive algumas variáveis ​​no final do alfabeto para que o regex que insere os sinais $ não altere a constante STDIN, então aqui está um pequeno glossário:

  • U: ponteiro de instruções
  • V: índice de opcode que está sendo testado no momento
  • W: palavra de instrução atual
  • X: os 8 registros de uso geral
  • Y: memória principal (cada bloco é baseado em 1, pois é assim que unpack()retorna as matrizes)
  • Z: id do próximo bloco de memória livre (acabará transbordando, mas a marca de areia usa apenas ~ 92 milhões)
  • A, B, C são os registros da instrução atual como na especificação

A divisão não assinada é um incômodo sutil ( *1é necessário para garantir que grandes números retornem ao int correto), mas o restante da aritmética é fácil de manter em 32 bits ORing o registro aritmético com 0 ( A|=0) após cada instrução.


Achei esse projeto realmente interessante, mas o esforço para minimizar a contagem de caracteres tornou-o lento e deselegante. Por isso, também fiz uma versão Java simples (sem golf), que pode completar a marca de areia em alguns minutos, em vez de demorar o dia todo:

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
fonte
Eu não acho que você precise ajustar o resultado da divisão para 32 bits, porque é sempre menor ou igual ao dividendo, que já está ajustado
mniip
Apenas por curiosidade, como é a aparência de não-destruído?
precisa saber é o seguinte
@mniip É um pouco diferente agora, mas preciso ter cuidado com a divisão, porque durante a divisão os números não são assinados e, a qualquer outro momento, são assinados.
Boann
3

Perl, 407

Parece que a pergunta pode parecer muito complexa, na verdade é muito simples.
Eu ainda sou muito novo em perl, de qualquer maneira aqui 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].";";}

Ele roda muito devagar, provavelmente 800x mais lento que o JITed x86_64.
Além disso, um amigo meu fez uma implementação de referência C

mniip
fonte
Isso é um problema no código C de referência ?: if(((Memory[++PC]>>28)&15) == 13) { Registers[(Memory[PC]>>25)&7] = (Memory[PC]&0x01ffffff);a instrução não é armazenada em cache; portanto, quaisquer opcodes que não sejam 13 pré-executariam a próxima instrução, não?
Luser droog
2

C, 924 838 825 696 646 623

Eu armazeno um "ponteiro" (deslocamento de byte) no registro designado bna instrução e uso qualquer registro que designe uma matriz no pseudocódigo da mesma maneira (ou inverter, em vez disso, para reconstituir um ponteiro) para acessar essa matriz posteriormente. Ainda precisa tentar o programa de teste ...

Editar: comentários adicionados.

Editar: instrução fixa 12. mude o ponteiro, não a instrução na memória. A contagem é com todos os comentários, recuos e novas linhas removidos.

Editar: parece estar em execução agora, supondo que eu esteja interpretando os resultados corretamente. :) A conclusão final foi que a matriz 0 é realmente referenciada pelo identificador 0, que pode ser encontrado em um registro não inicializado. Uma pequena máquina muito distorcida! :)

Edit: reescreve o aparelho de depuração para usar em writevez de printf.... A idéia aqui é remover os bugs. :) Editar: putchar() e getchar()também não gostam de sbrk. Agora funciona e 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;}}}

Apenas para little-endian, há uma versão 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;}}}

Recuado e comentado, com aparelhagem de depuração comentada (estendida).

//#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
fonte
As alças da matriz são 100% opacas. Não importa o que você passa, o programa deve usar o mesmo valor ao acessar matrizes. PS eu apenas tentei compilá-lo, você está faltando um par inclui. PPS você já o compilou? o que é lbreake como você pode unary- *umint
mniip
Sim. Um pouco ansioso. :) O código atualizado é compilado com o gcc no Cygwin.
Luser droog
@ mniip Portanto, é apenas o array 0 designado por "número"?
Luser droog
acaba de compilar isso, ele só executa 2 instruções fora de Sandmark: d000108f c0000030e então sai
mniip
Corrigi um bug. Ele executa 7 instruções agora antes de parar.
Luser droog