Thực hiện trình giả lập Universal Machine


13

Mục tiêu là viết một chương trình đầy đủ mô phỏng Máy vạn năng từ ICFP 2006 với mã ngắn nhất. Universal Machine có một bộ hướng dẫn rất đơn giản được giải thích ở đây . Trình giả lập phải đọc tên tệp từ đối số dòng lệnh và chạy tệp dưới dạng chương trình, vì vậy ngôn ngữ của bạn phải hỗ trợ các đối số dòng lệnh và stdin / out theo một cách nào đó. Trình giả lập phải hoàn thành dấu cát trong khoảng thời gian hợp lý (không phải hàng thập kỷ). Dưới đây là một lời giải thích ngắn về bộ hướng dẫn:

Máy có tám thanh ghi, mỗi thanh giữ một số nguyên không dấu 32 bit.
Máy chứa một tập hợp các mảng gồm các ô nguyên không dấu 32 bit.
Nói ngắn gọn, lệnh cấp phát trả về một uint 32 bit mờ, là phần xử lý cho mảng đã tạo, có kích thước tĩnh và chứa các phần tử uint 32 bit.
Mảng 0'th biểu thị chương trình. Nó được tải từ một tập tin lớn khi khởi động.
Ngoài ra còn có một Con trỏ lệnh trỏ tới một ô trong mảng 0.
Trên mỗi bước, một lệnh được đọc từ ô mà Con trỏ trỏ tới và Con trỏ được đặt trước khi mọi thứ được thực hiện.
4 bit quan trọng nhất đại diện cho opcode.
Nếu opcode là 13, thì 3 bit tiếp theo đại diện cho thanh ghi và 25 bit còn lại biểu thị số được ghi vào thanh ghi đã nói.
Mặt khác, 9 bit có ý nghĩa nhỏ nhất đại diện cho ba thanh ghi, giả sử A, B và C, trong đó C được biểu thị bằng 3 bit có trọng số thấp nhất.
Sau đó tùy thuộc vào opcode, điều sau đây xảy ra:
0. A = B trừ khi 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. Trình giả lập thoát
8. B = allocate (C)
9. deallocate (C)
10. xuất một ký tự từ C sang stdout
11. nhập một ký tự từ stdin vào C
12. sao chép mảng B vào mảng 0 và đặt Con trỏ thành C

Tôi đã viết một triển khai (ab) phức tạp không cần thiết nhưng hoàn toàn nhanh chóng bằng cách sử dụng lắp ráp x86_64 (trò vui bắt đầu trong emit ()) , điều này chắc chắn sẽ giúp bạn nếu bạn hiểu sai một số khía cạnh của Máy.


Bạn phải quyết định xem đây có phải là môn đánh gôn hay thi đấu phổ biến không. Họ là độc quyền.
Howard

@ Xin chào, tôi hiểu rồi, cảm ơn
mniip 28/12/13

Nếu tôi không nhầm, máy được mô tả là Big Endian chứ không phải Little Endian.
Hasturkun

@Hasturkun d'oh Tôi luôn làm phiền những điều này, tôi cứ nghĩ Big Endian là viết tắt của "kết thúc bằng byte lớn hơn"
mniip

1
@mniip Big Endian và Little Endian là những thuật ngữ mượn từ Gulliver's Travels. Những người nhỏ bé của Lilliput đã có chiến tranh với những người nhỏ bé ở Blefuscu, bởi vì người Lilliputian là "Người lớn cuối cùng", người tin rằng bạn nên ăn phần lớn của một quả trứng luộc trước, và Blefuscans tin điều ngược lại. Gulliver's Travels ban đầu là một cuốn tiểu thuyết nghiêm túc của Jonathan swift. Tác giả đã bình luận về sự ngu ngốc của chiến tranh về sự khác biệt chính trị và tôn giáo. Gulliver bị buộc phải rời đi sau khi bị buộc tội phản quốc vì từ chối giúp đỡ trong cuộc chiến.
Cấp sông St

Câu trả lời:


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

* Được tân trang lại *. Nó nhỏ như tôi có thể có được bây giờ. Tôi đã giữ một số biến ở phía xa của bảng chữ cái để biểu thức chính xác chèn dấu $ không mang lại hằng số STDIN, vì vậy đây là một thuật ngữ nhỏ:

  • Con trỏ lệnh
  • V: chỉ số của opcode hiện đang được thử nghiệm
  • W: từ hướng dẫn hiện tại
  • X: 8 thanh ghi mục đích chung
  • Y: bộ nhớ chính (mỗi khối dựa trên 1, vì đó là cách unpack()trả về mảng)
  • Z: id của khối bộ nhớ trống tiếp theo (cuối cùng sẽ tràn, nhưng sandmark chỉ sử dụng ~ 92 triệu)
  • A, B, C là các thanh ghi của lệnh hiện tại như trong spec

Phân chia không được ký là một phiền toái tinh vi ( *1cần thiết để đảm bảo rằng các số lớn được trả về đúng int) nhưng phần còn lại của số học rất dễ giữ 32 bit bằng cách OR đăng ký số học với 0 ( A|=0) sau mỗi lệnh.


Tôi thấy dự án này thực sự thú vị nhưng việc cố gắng giảm thiểu số lượng nhân vật khiến nó chậm và không phù hợp, vì vậy tôi cũng đã tạo một phiên bản Java đơn giản (không phải chơi gôn), có thể hoàn thành dấu cát trong vài phút thay vì mất cả ngày:

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

Tôi không nghĩ rằng bạn cần điều chỉnh kết quả của việc chia thành 32 bit bởi vì nó luôn nhỏ hơn hoặc bằng với cổ tức, đã được điều chỉnh
mniip

Chỉ vì tò mò, nó trông như thế nào?
Tim Seguine

@mniip Bây giờ có một chút khác biệt, nhưng tôi cần cẩn thận với việc phân chia vì trong quá trình phân chia, các số không được ký và tại mọi thời điểm khác chúng được ký.
Boann

3

Perl, 407

Có vẻ như câu hỏi có vẻ quá phức tạp, thực ra nó rất đơn giản.
Tôi vẫn còn rất mới với perl, dù sao thì đây là

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

Nó chạy rất chậm, có thể chậm hơn 800 lần so với JITed x86_64.
Ngoài ra, một người bạn của tôi đã thực hiện tham chiếu C


Đây có phải là một vấn đề trong mã C tham chiếu không?: Lệnh if(((Memory[++PC]>>28)&15) == 13) { Registers[(Memory[PC]>>25)&7] = (Memory[PC]&0x01ffffff);không được lưu trong bộ nhớ cache, vì vậy bất kỳ mã nào không 13 sẽ thực hiện trước lệnh tiếp theo, phải không?
kẻ lừa đảo người lái xe

2

C, 924 838 825 696 646 623

Tôi lưu trữ một "con trỏ" (bù byte) trong thanh ghi được chỉ định btrong lệnh và sử dụng bất kỳ thanh ghi nào chỉ định một mảng trong mã giả theo cách tương tự (hoặc ngược lại, để khôi phục một con trỏ) để truy cập vào mảng đó sau này. Vẫn cần phải thử chương trình thử nghiệm ...

Chỉnh sửa: thêm ý kiến.

Chỉnh sửa: hướng dẫn cố định 12. thay đổi con trỏ, không phải hướng dẫn trong bộ nhớ. Đếm với tất cả các bình luận, thụt lề và dòng mới bị xóa.

Chỉnh sửa: Có vẻ như nó đang chạy ngay bây giờ, giả sử tôi đang diễn giải kết quả chính xác. :) Nhận thức cuối cùng là mảng 0 thực sự được tham chiếu bởi tay cầm 0, có thể được tìm thấy trong một thanh ghi chưa được khởi tạo. Một cỗ máy nhỏ rất xoắn! :)

Chỉnh sửa: viết lại bộ máy gỡ lỗi để sử dụng writethay vì printf.... Ý tưởng ở đây là loại bỏ lỗi. :) Chỉnh sửa: putchar()getchar()cũng không có nos với sbrk. Bây giờ nó hoạt động, và xuất hiện khá nhanh.

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

Chỉ dành cho người cuối cùng, có phiên bản 611 ký tự.

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

Được thụt lề và nhận xét, với (mở rộng) nhận xét bộ máy gỡ lỗi.

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

Tay cầm mảng mờ 100%. Bất kể bạn vượt qua nó là gì, chương trình được cho là sử dụng cùng một giá trị khi truy cập các mảng. PS tôi vừa thử biên dịch nó, bạn đang thiếu một vài bao gồm. PPS bạn đã bao giờ biên dịch nó? những gì lbreakvà làm thế nào bạn có thể unary- *anint
mniip

Đúng. Một chút quá háo hức. :) Cập nhật mã biên dịch với gcc trên Cygwin.
luser droog

@mniip Vậy chỉ có mảng 0 được chỉ định bởi "số"?
luser droog

chỉ cần biên dịch nó, nó chỉ thực hiện 2 hướng dẫn ngoài sandmark: d000108f c0000030 và sau đó thoát
mniip

Tôi đã sửa một lỗi. Nó thực hiện 7 hướng dẫn bây giờ trước khi tạm dừng.
kẻ lừa đảo người lái xe
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.