ใช้เครื่องมือจำลองเครื่องสากล


13

เป้าหมายคือการเขียนโปรแกรมเต็มรูปแบบที่จำลองเครื่องสากลจาก ICFP 2006 ด้วยรหัสที่สั้นที่สุด เครื่องสากลมีชุดการเรียนการสอนที่ง่ายมากอธิบายที่นี่ อีมูเลเตอร์ต้องอ่านชื่อไฟล์จากอาร์กิวเมนต์บรรทัดคำสั่งและเรียกใช้ไฟล์เป็นโปรแกรมดังนั้นภาษาของคุณจะต้องสนับสนุนอาร์กิวเมนต์บรรทัดคำสั่งและ stdin / out อย่างใด จำลองที่มีการดำเนินการsandmarkภายในเวลาอันสมควร (ไม่ทศวรรษที่ผ่านมา) นี่คือคำอธิบายสั้น ๆ ของชุดคำสั่ง:

เครื่องมีการลงทะเบียนแปดครั้งแต่ละคนมีจำนวนเต็ม 32 บิตที่ไม่ได้ลงชื่อ
เครื่องเก็บชุดอาร์เรย์ที่จัดทำดัชนีไว้ของเซลล์เลขจำนวนเต็ม 32 บิตที่ไม่ได้ลงชื่อ
พูดสั้น ๆ คำแนะนำในการจัดสรรผลตอบแทนทึบแสง 32 บิตซึ่งเป็นมือจับไปยังอาร์เรย์ที่สร้างขึ้นซึ่งมีขนาดคงที่และถือองค์ประกอบ uint 32 บิต
อาร์เรย์ 0'th หมายถึงโปรแกรม มันถูกโหลดจากไฟล์ big-endian เมื่อเริ่มต้น
นอกจากนี้ยังมี Instruction Pointer ซึ่งชี้ไปที่เซลล์ในอาร์เรย์ 0
ในแต่ละขั้นตอนคำสั่งจะอ่านจากเซลล์ที่ตัวชี้ชี้ไปที่และตัวชี้จะถูกฝังก่อนที่จะทำอะไร
บิตที่สำคัญที่สุด 4 บิตแสดงถึง opcode
หาก opcode เท่ากับ 13 ดังนั้นบิต 3 ถัดไปจะเป็นตัวแทนของการลงทะเบียนและอีก 25 บิตจะแทนตัวเลขที่เขียนไว้ในการลงทะเบียนดังกล่าว
มิฉะนั้นบิตที่มีนัยสำคัญน้อยที่สุด 9 รายการจะเป็นตัวแทนของสามรีจิสเตอร์พูด A, B และ C โดยที่ C แสดงโดยบิตที่มีนัยสำคัญน้อยที่สุด 3 รายการ
จากนั้นขึ้นอยู่กับ opcode สิ่งต่อไปนี้เกิดขึ้น:
0. A = B เว้นแต่ 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. โปรแกรมจำลองออกจาก
8. B = จัดสรร (C)
9. deallocate (C)
10. ส่งอักขระจาก C ถึง stdout
11. ป้อนอักขระ จาก stdin เป็น C
12. คัดลอกอาร์เรย์ B ลงในอาร์เรย์ 0 และตั้งค่าตัวชี้เป็น C

ฉันได้เขียนการใช้งานที่ซับซ้อนโดยไม่จำเป็น แต่รวดเร็วอย่างสมบูรณ์ (ab) โดยใช้ชุดประกอบ jitted x86_64 (ความสนุกเริ่มต้นในการปล่อย ())ซึ่งจะช่วยคุณได้อย่างแน่นอนหากคุณเข้าใจบางแง่มุมของเครื่องจักร


คุณต้องตัดสินใจว่าสิ่งนี้ควรเป็นโค้ดกอล์ฟหรือการประกวดความนิยม พวกเขาพิเศษ
โฮเวิร์ด

@Howard ฉันเห็นขอบคุณ
mniip

ถ้าฉันไม่เข้าใจผิดเครื่องอธิบายว่าเป็น Big Endian ไม่ใช่ Little Endian
Hasturkun

@Hasturkun d'โอ้ฉันมักจะเลอะเหล่านี้ผมคิดบิ๊ก Endian ย่อมาจาก "สิ้นสุดในไบต์ที่ใหญ่กว่า"
mniip

1
@mniip Big Endian และ Little Endian เป็นคำศัพท์ที่ยืมมาจากการเดินทางของ Gulliver คนตัวเล็กของ Lilliput ทำสงครามกับคนตัวเล็ก ๆ ของ 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",'])));

* ปรับปรุงใหม่อีกครั้ง * มันเล็กมากเท่าที่ฉันจะทำได้ตอนนี้ ฉันเก็บตัวแปรไว้ที่ส่วนท้ายสุดของตัวอักษรเพื่อให้ regex ที่แทรกเครื่องหมาย $ ไม่ได้ทำให้ค่าคงที่ของ STDIN ลดลงดังนั้นนี่เป็นอภิธานศัพท์เล็กน้อย:

  • U: ตัวชี้คำสั่ง
  • V: ดัชนีของ opcode ที่กำลังทดสอบอยู่
  • W: คำสอนปัจจุบัน
  • X: การลงทะเบียนวัตถุประสงค์ทั่วไป 8 ข้อ
  • Y: หน่วยความจำหลัก (แต่ละบล็อคใช้ 1 เนื่องจากเป็นวิธีunpack()ส่งคืนอาร์เรย์)
  • Z: id ของบล็อกหน่วยความจำว่างถัดไป (ในที่สุดจะล้น แต่แซนมาร์คใช้เพียง ~ 92 ล้าน)
  • A, B, C คือการลงทะเบียนของคำสั่งปัจจุบันตามที่ระบุไว้

ส่วนที่ไม่ได้ลงชื่อเป็นรำคาญที่ลึกซึ้ง (คน*1เป็นสิ่งจำเป็นเพื่อให้มั่นใจว่าจำนวนมากโยนกลับไปที่ int ที่ถูกต้อง) แต่ส่วนที่เหลือของเลขคณิตเป็นเรื่องง่ายที่จะเก็บ 32 บิตโดย ORing ลงทะเบียนกับทางคณิตศาสตร์ (0 A|=0) หลังจากแต่ละคำสั่ง


ฉันพบว่าโครงการนี้น่าสนใจจริงๆ แต่พยายามลดจำนวนอักขระให้ช้าลงและไม่เหมาะสมดังนั้นฉันจึงสร้าง 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

Perl, 407

ดูเหมือนว่าคำถามอาจดูซับซ้อนเกินไปจริงๆแล้วมันง่ายมาก
ฉันยังใหม่กับ perl มากอยู่ดี

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 ถึง 800x
นอกจากนี้เพื่อนของฉันยังทำการอ้างอิง C


นี่เป็นปัญหาในรหัส C อ้างอิงหรือไม่: if(((Memory[++PC]>>28)&15) == 13) { Registers[(Memory[PC]>>25)&7] = (Memory[PC]&0x01ffffff);คำสั่งไม่ได้ถูกแคชดังนั้น opcodes ใด ๆ ที่ไม่ใช่ 13 จะดำเนินการคำสั่งล่วงหน้าล่วงหน้าใช่หรือไม่
luser droog

2

ค, 924 838 825 696 646 623

ฉันเก็บ "พอยน์เตอร์" (byte-offset) ในรีจิสเตอร์ที่กำหนดไว้bในคำสั่งและใช้สิ่งที่รีจิสเตอร์กำหนดอาเรย์ใน pseudocode ในลักษณะเดียวกัน ยังคงต้องลองโปรแกรมทดสอบ ...

แก้ไข:เพิ่มความคิดเห็น

แก้ไข:คำสั่งคงที่ 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

ใช่. กระตือรือร้นเล็กน้อยเกินไป :) อัปเดตโค้ดคอมไพล์ด้วย gcc บน Cygwin
luser droog

@mniip ดังนั้นมันคืออาร์เรย์ 0เท่านั้นที่ถูกกำหนดโดย "number"?
luser droog

เพียงรวบรวมมันเพียงดำเนินการ 2 คำแนะนำจาก sandmark: d000108f c0000030แล้วออก
mniip

ฉันแก้ไขข้อผิดพลาดหนึ่งข้อ มันรันคำสั่ง 7 ตอนนี้ก่อนที่จะหยุด
luser droog
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.