Universal Machine 에뮬레이터 구현


13

목표는 ICFP 2006의 Universal Machine을 가장 짧은 코드로 에뮬레이트하는 전체 프로그램을 작성하는 것입니다. Universal Machine에는 여기에 설명 된 매우 간단한 명령 세트가 있습니다 . 에뮬레이터는 명령 줄 인수에서 파일 이름을 읽고 파일을 프로그램으로 실행해야하므로 언어는 명령 줄 인수 및 stdin / out을 지원해야합니다. 에뮬레이터는 적절한 시간 (수십 년이 아닌) 내에 샌드 마크 를 완료해야합니다 . 다음은 명령어 세트에 대한 간단한 설명입니다.

이 기계에는 각각 32 비트 부호없는 정수를 보유하는 8 개의 레지스터가 있습니다.
이 기계는 32 비트 부호없는 정수 셀로 구성된 인덱스 배열 세트를 보유합니다.
간단히 말해서, 할당 명령은 생성 된 배열의 핸들 인 정적 32 비트 uint 요소를 포함하는 불투명 한 32 비트 uint를 반환합니다.
0 번째 배열은 프로그램을 나타냅니다. 시작시 빅 엔디안 파일에서로드됩니다.
0 배열의 셀을 가리키는 명령 포인터도 있습니다.
각 단계에서 포인터가 가리키는 셀에서 명령을 읽고 포인터가 수행되기 전에 삽입됩니다.
최상위 4 비트는 opcode를 나타냅니다.
오피 코드가 13이면, 다음 3 비트는 레지스터를 나타내고, 다른 25 개는 상기 레지스터에 기록 된 수를 나타낸다.
그렇지 않으면 9 개의 최하위 비트는 3 개의 레지스터, 즉 A, B 및 C를 나타내며, 여기서 C는 3 개의 최하위 비트로 표시됩니다.
그런 다음 오피 코드에 따라 다음이 발생합니다.
C == 0이 아닌 한 0. A = B
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. 에뮬레이터가 종료합니다.
B = assign (C)
9. deallocate (C)
10. C에서 stdout으로 문자를 출력합니다
. 11. 문자를 입력합니다 stdin에서 C로
12. 배열 B를 0 배열에 복사하고 포인터를 C로 설정하십시오.

필자는 x86_64 jitted 어셈블리 ( 펀드 에서 emit ()로 시작하는 재미 )를 사용하여 불필요하게 복잡하지만 완전히 빠른 구현 (ab)을 작성했습니다 .


이것이 코드 골프인지 인기 콘테스트인지 결정해야합니다. 그들은 독점적입니다.
Howard

@Howard I see, thanks
mniip

내가 실수하지 않으면 기계는 Little Endian이 아니라 Big Endian으로 묘사됩니다.
Hasturkun

@Hasturkun d' oh 나는 항상 이것들을 망쳐 놓는다. 나는 Big Endian이 "더 큰 바이트로 끝나는"을 의미
한다고 생각한다

1
@mniip Big Endian과 Little Endian은 Gulliver 's Travels에서 빌린 용어입니다. Lilliputians의 작은 사람들은 Blefuscu의 작은 사람들과 전쟁을 벌였습니다. Lilliputians는 삶은 계란의 큰 끝을 먼저 먹어야한다고 믿었던 "Big Endians"이었고 Blefuscans는 그 반대를 믿었습니다. 원래 걸리버 여행은 조나단 스위프트의 진지한 소설이었습니다. 작가는 정치적, 종교적 차이를 놓고 싸우는 어리 석음에 대해 언급했다. 걸리버는 반역죄로 기소 된 후 전쟁에서 도움을 거부 한 채 강제로 떠났다.
레벨 리버 세인트

답변:


6

PHP : 443 416  384 바이트

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

* 다시 상승 *. 내가 지금 얻을 수있는 한 작습니다. $ 기호를 삽입하는 정규식이 STDIN을 일정하게 유지하지 않도록 일부 변수를 알파벳의 맨 아래에 유지했습니다. 그래서 약간의 용어가 있습니다.

  • U : 명령 포인터
  • V : 현재 테스트중인 opcode의 색인
  • W : 현재 지시어
  • X : 8 개의 범용 레지스터
  • Y : 메인 메모리 (각 블록은 1 기반이므로 unpack()배열을 반환 하는 방식이므로 )
  • Z : 다음 사용 가능한 메모리 블록의 id (결과적으로 오버 플로우되지만 샌드 마크는 ~ 92 백만 만 사용함)
  • A, B, C는 사양에서와 같이 현재 명령어의 레지스터입니다.

부호없는 나누기는 미묘한 성가신 ( *1많은 숫자가 올바른 int로 다시 캐스팅되도록 보장해야 함)이지만 나머지 A|=0명령은 각 명령 후에 산술 레지스터를 0 ( ) 으로 OR하여 32 비트를 유지하기 쉽습니다 .


이 프로젝트가 정말 흥미롭지 만 문자 수를 최소화하려고 노력하면 느리고 우아하지 않았으므로 간단한 (골프가 아닌) Java 버전도 만들었습니다.이 버전은 하루 종일 걸리지 않고 몇 분 안에 샌드 마크를 완성 할 수 있습니다.

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

나는 항상보다-작거나-평등을 이미 조정 배당이기 때문에 당신이 32 비트 부문의 결과를 조정할 필요가 있다고 생각하지 않습니다
mniip

호기심만으로도 골퍼가 아닌 것처럼 보입니까?
Tim Seguine

@mniip 그것은 조금 다르게 배치되었지만, 나누는 동안 숫자는 부호가 없으며 다른 순간에는 부호가 있기 때문에 나누기에주의해야합니다.
Boann

3

펄, 407

질문이 너무 복잡해 보일 수도 있지만 실제로는 매우 간단합니다.
그래도 여전히 펄을 처음 접합니다. 어쨌든 여기 있습니다.

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

JITed x86_64보다 800 배 느리게 실행됩니다.
또한 내 친구가 참조 C 구현을 만들었습니다.


이것이 참조 C 코드의 문제입니까? : if(((Memory[++PC]>>28)&15) == 13) { Registers[(Memory[PC]>>25)&7] = (Memory[PC]&0x01ffffff);명령어가 캐시되지 않으므로 13이 아닌 opcode는 다음 명령어를 미리 실행합니다.
luser droog

2

씨, 924 838 825 696 646 623

나는 b명령어에 지정된 레지스터에 "포인터"(바이트 오프셋)를 저장하고, 나중에 레지스터에 액세스 할 때와 동일한 방식으로 (또는 포인터를 재구성하기 위해 반대로) 의사 코드에서 배열을 지정하는 레지스터를 사용합니다. 여전히 테스트 프로그램을 시도해야합니다 ...

편집 : 의견을 추가했습니다.

편집 : 고정 명령 12. 메모리의 명령이 아닌 포인터를 변경하십시오. 개수는 모든 주석, 들여 쓰기 및 줄 바꿈이 제거 된 상태입니다.

편집 : 결과를 올바르게 해석한다고 가정하면 현재 실행중인 것으로 보입니다. :) 최종 구현은 0 배열 이 실제로 핸들 0에 의해 참조되고 초기화되지 않은 레지스터에서 발견 될 수 있다는 것입니다. 꼬인 작은 기계! :)

편집 : ... write대신 사용할 디버깅 장치를 다시 작성했습니다 printf. 여기서 아이디어는 버그 를 제거하는 것 입니다. :) 편집 : putchar()getchar()도 아무-번호 없습니다 sbrk. 이제 작동하고 매우 빠르게 나타납니다.

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

리틀 엔디안 전용의 경우 611 자 버전이 있습니다.

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

(확장 된) 주석 처리 된 디버깅 장치로 들여 쓰기 및 주석 처리됨.

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

배열 핸들은 100 % 불투명합니다. 무엇을 전달하든 프로그램은 배열에 액세스 할 때 동일한 값을 사용해야합니다. 추신 : 방금 컴파일을 시도했지만 몇 가지 내용이 누락되었습니다. PPS를 컴파일 한 적이 있습니까? 무엇을 lbreak어떻게 당신은 unary- 수*int
mniip

예. 조금 간절한. :) 업데이트 된 코드는 Cygwin에서 gcc로 컴파일됩니다.
luser droog 님이

@mniip "number"로 지정된 배열 0 뿐입니 까?
luser droog

그냥 컴파일하면 샌드 마크에서 2 개의 명령 만 실행 d000108f c0000030한 다음 종료됩니다.
nini

하나의 버그를 수정했습니다. 이제 정지하기 전에 7 개의 명령을 실행합니다.
luser droog
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.