เครื่องเสมือน 8 บิต


31

พื้นหลัง

ฉันชอบชิป 8 บิต 6502 แบบเก่าของฉัน มันสนุกมากที่จะไขความท้าทายบางอย่างที่นี่บน PPCG ในรหัสเครื่อง 6502 แต่บางสิ่งที่ควรจะง่าย (เช่นอ่านข้อมูลหรือส่งออกไปยัง stdout) นั้นยุ่งยากโดยไม่จำเป็นในรหัสเครื่อง ดังนั้นจึงมีความคิดคร่าวๆในใจของฉัน: คิดค้นเครื่องเสมือน 8 บิตของตัวเองที่ได้รับแรงบันดาลใจจาก 6502 แต่ด้วยการออกแบบที่ปรับเปลี่ยนให้ใช้งานได้มากกว่าสำหรับความท้าทาย เริ่มใช้บางสิ่งฉันรู้ว่านี่อาจเป็นความท้าทายที่ดีถ้าการออกแบบ VM ลดลงจนเหลือน้อยที่สุด :)

งาน

ใช้เครื่องเสมือน 8 บิตที่สอดคล้องกับข้อกำหนดต่อไปนี้ นี่คือดังนั้นการติดตั้งที่มีไบต์น้อยที่สุดจะชนะ

อินพุต

การใช้งานของคุณควรใช้อินพุตต่อไปนี้:

  • ไบต์ที่ไม่ได้ลงนามเดียวpcนี่คือตัวนับโปรแกรมเริ่มต้น (ที่อยู่ในหน่วยความจำที่ VM เริ่มดำเนินการ0- ตาม)

  • รายการไบต์ที่มีความยาวสูงสุดของ256รายการนี่คือ RAM สำหรับเครื่องเสมือน (ที่มีเนื้อหาเริ่มต้น)

คุณสามารถรับอินพุตนี้ในรูปแบบที่สมเหตุสมผล

เอาท์พุต

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

CPU เสมือน

ซีพียูเสมือนมี

  • ตัวนับโปรแกรม 8 บิต
  • ตัวสะสมแบบ 8 บิตที่เรียกว่าAและ
  • ลงทะเบียนดัชนี 8 Xบิตที่เรียกว่า

มีสถานะสามสถานะ:

  • Z - ตั้งค่าสถานะเป็นศูนย์หลังจากการดำเนินการบางอย่างส่งผล 0
  • N - ตั้งค่าสถานะเชิงลบหลังจากที่ผลการดำเนินงานบางอย่างในจำนวนลบ (iow บิต 7 ของผลการตั้งค่า)
  • C - ค่าสถานะพกถูกตั้งค่าโดยการเพิ่มเติมและกะสำหรับบิต "หายไป" ของผลลัพธ์

เมื่อเริ่มต้นแฟล็กจะถูกล้างทั้งหมดตัวนับโปรแกรมจะถูกตั้งค่าเป็นค่าที่กำหนดและเนื้อหาของAและXไม่แน่นอน

ค่า 8 บิตแสดงถึงอย่างใดอย่างหนึ่ง

  • ที่ไม่ได้ลงนามจำนวนเต็มในช่วง[0..255]
  • ลงนามจำนวนเต็ม 2 สมบูรณ์ในช่วง[-128..127]

ขึ้นอยู่กับบริบท หากการดำเนินการมากเกินไปหรือน้อยเกินไปค่าจะถูกล้อมรอบ (และในกรณีที่มีการเพิ่มค่าสถานะการพกพาจะได้รับผลกระทบ)

การสิ้นสุด

เครื่องเสมือนจะหยุดทำงานเมื่อ

  • HLTคำแนะนำถึง
  • เข้าถึงที่อยู่หน่วยความจำที่ไม่มีอยู่
  • ตัวนับโปรแกรมทำงานนอกหน่วยความจำ (โปรดทราบว่ามันจะไม่พันรอบแม้ว่า VM จะได้รับหน่วยความจำเต็ม 256 ไบต์)

โหมดที่อยู่

  • โดยนัย - คำสั่งไม่มีข้อโต้แย้งตัวถูกดำเนินการโดยนัย
  • ทันที - ตัวถูกดำเนินการเป็นไบต์โดยตรงหลังจากการเรียนการสอน
  • สัมพัทธ์ - (สำหรับการแตกแขนงเท่านั้น) ไบต์หลังจากคำสั่งถูกลงชื่อ (ส่วนเติมเต็ม 2) และกำหนดออฟเซ็ตเพื่อเพิ่มลงในตัวนับโปรแกรมหากมีการแยกสาขา 0เป็นที่ตั้งของคำแนะนำต่อไปนี้
  • แน่นอน - ไบต์หลังจากคำสั่งคือที่อยู่ของตัวถูกดำเนินการ
  • จัดทำดัชนี - ไบต์หลังจากคำสั่งบวกX(การลงทะเบียน) คือที่อยู่ของตัวถูกดำเนินการ

คำแนะนำ

การเรียนการสอนในแต่ละประกอบด้วย opcode (หนึ่งไบต์) และในโหมดที่อยู่ได้ทันที , ญาติ , แน่นอนและการจัดทำดัชนีไบต์อาร์กิวเมนต์ที่สอง เมื่อ CPU เสมือนดำเนินการคำสั่งจะเพิ่มตัวนับโปรแกรมตาม (โดย1หรือ2 )

opcodes ทั้งหมดที่แสดงในที่นี้เป็นเลขฐานสิบหก

  • LDA - ตัวถูกดำเนินการโหลดเข้า A

    • Opcodes: ทันที: 00, สัมบูรณ์: 02, จัดทำดัชนี:04
    • ธง: Z,N
  • STA- เก็บตัวAถูกดำเนินการ

    • Opcodes: ทันที: 08, สัมบูรณ์: 0a, จัดทำดัชนี:0c
  • LDX - ตัวถูกดำเนินการโหลดเข้า X

    • Opcodes: ทันที: 10, แน่นอน:12, จัดทำดัชนี:14
    • ธง: Z,N
  • STX- เก็บตัวXถูกดำเนินการ

    • Opcodes: ทันที: 18, สัมบูรณ์: 1a, จัดทำดัชนี:1c
  • AND- bitwise และ of Aและ operand เป็นA

    • Opcodes: ทันที: 30, แน่นอน:32, จัดทำดัชนี:34
    • ธง: Z,N
  • ORA- bitwise หรือของAและ operand เป็นA

    • Opcodes: ทันที: 38, แน่นอน:3a, จัดทำดัชนี:3c
    • ธง: Z,N
  • EOR- bitor xor (พิเศษหรือ) ของAและถูกดำเนินการไปA

    • Opcodes: ทันที: 40, แน่นอน:42, จัดทำดัชนี:44
    • ธง: Z,N
  • LSR - ตรรกะเลื่อนไปทางขวาเลื่อนบิตของตัวถูกดำเนินการทั้งหมดที่เดียวไปทางขวาบิต 0 จะถูกนำไปใช้

    • Opcodes: ทันที: 48, สัมบูรณ์: 4a, จัดทำดัชนี:4c
    • ธง: Z, N,C
  • ASL - การเลื่อนเลขคณิตไปทางซ้ายเลื่อนบิตของตัวถูกดำเนินการทั้งหมดที่เดียวไปทางซ้ายบิต 7 จะถูกดำเนินการ

    • Opcodes: ทันที: 50, สัมบูรณ์: 52, จัดทำดัชนี:54
    • ธง: Z, N,C
  • ROR - หมุนไปทางขวาเลื่อนบิตของตัวถูกดำเนินการทั้งหมดที่เดียวไปทางขวาพกไปที่บิต 7, บิต 0 ไปเพื่อดำเนินการ

    • Opcodes: ทันที: 58, สัมบูรณ์: 5a, จัดทำดัชนี:5c
    • ธง: Z, N,C
  • ROL - หมุนไปทางซ้ายเลื่อนบิตทั้งหมดของตัวถูกดำเนินการไปทางซ้ายหนึ่งจุดไปยังบิต 0, บิต 7 ไปสู่การถือ

    • Opcodes: ทันที: 60, สัมบูรณ์: 62, จัดทำดัชนี:64
    • ธง: Z, N,C
  • ADC- เพิ่มด้วยการดำเนินการ, ตัวถูกดำเนินการบวกการดำเนินการเพิ่มไปยังAการดำเนินการตั้งอยู่บนล้น

    • Opcodes: ทันที: 68, สัมบูรณ์: 6a, จัดทำดัชนี:6c
    • ธง: Z, N,C
  • INC - ตัวถูกดำเนินการที่เพิ่มขึ้นหนึ่ง

    • Opcodes: ทันที: 78, สัมบูรณ์: 7a, จัดทำดัชนี:7c
    • ธง: Z,N
  • DEC - ตัวถูกดำเนินการลดลงหนึ่ง

    • Opcodes: ทันที: 80, สัมบูรณ์: 82, จัดทำดัชนี:84
    • ธง: Z,N
  • CMP- เปรียบเทียบAกับตัวถูกดำเนินการโดยการลบตัวถูกดำเนินการจากAลืมผลลัพธ์ Carry ถูกลบล้างอันเดอร์โฟลว์หรือไม่

    • Opcodes: ทันที: 88, สัมบูรณ์: 8a, จัดทำดัชนี:8c
    • ธง: Z, N,C
  • CPX- เทียบX- เช่นเดียวCMPสำหรับX

    • Opcodes: ทันที: 90, สัมบูรณ์: 92, จัดทำดัชนี:94
    • ธง: Z, N,C
  • HLT - ยุติ

    • Opcodes: โดยนัย: c0
  • INX- เพิ่มขึ้นทีละXหนึ่ง

    • Opcodes: โดยนัย: c8
    • ธง: Z,N
  • DEX- ลดลงXหนึ่ง

    • Opcodes: โดยนัย: c9
    • ธง: Z,N
  • SEC - ตั้งธงพก

    • Opcodes: โดยนัย: d0
    • ธง: C
  • CLC - ธงพกพาที่ชัดเจน

    • Opcodes: โดยนัย: d1
    • ธง: C
  • BRA - สาขาเสมอ

    • Opcodes: ญาติ: f2
  • BNE- สาขาถ้าZล้างธง

    • Opcodes: ญาติ: f4
  • BEQ- สาขาถ้าZตั้งค่าสถานะ

    • Opcodes: ญาติ: f6
  • BPL- สาขาถ้าNล้างธง

    • Opcodes: ญาติ: f8
  • BMI- สาขาถ้าNตั้งค่าสถานะ

    • Opcodes: ญาติ: fa
  • BCC- สาขาถ้าCล้างธง

    • Opcodes: ญาติ: fc
  • BCS- สาขาถ้าCตั้งค่าสถานะ

    • Opcodes: ญาติ: fe

opcodes

พฤติกรรมของ VM นั้นไม่ได้กำหนดไว้หากพบว่ามี opcode ที่ไม่ได้จับคู่กับคำสั่งที่ถูกต้องจากรายการด้านบน

ตามคำขอของ Jonathan Allanคุณสามารถเลือก opcodes ของคุณเองแทน opcodes ที่แสดงในหัวข้อคำแนะนำ หากคุณทำเช่นนั้นคุณจะต้องเพิ่มการจับคู่แบบเต็มใน opcode ที่ใช้ด้านบนในคำตอบของคุณ

การจับคู่ควรเป็นไฟล์เลขฐานสิบหกที่มีคู่<official opcode> <your opcode>เช่นหากคุณแทนที่ opcodes สองรายการ:

f4 f5
10 11

บรรทัดใหม่ไม่สำคัญที่นี่

กรณีทดสอบ (opcodes อย่างเป็นทางการ)

// some increments and decrements
pc:     0
ram:    10 10 7a 01 c9 f4 fb
output: 10 20 7a 01 c9 f4 fb

// a 16bit addition
pc:     4
ram:    e0 08 2a 02 02 00 6a 02 0a 00 02 01 6a 03 0a 01
output: 0a 0b 2a 02 02 00 6a 02 0a 00 02 01 6a 03 0a 01

// a 16bit multiplication
pc:     4
ram:    5e 01 28 00 10 10 4a 01 5a 00 fc 0d 02 02 d1 6a 21 0a 21 02 03 6a 22 0a 22 52
        02 62 03 c9 f8 e6 c0 00 00
output: 00 00 00 00 10 10 4a 01 5a 00 fc 0d 02 02 d1 6a 21 0a 21 02 03 6a 22 0a 22 52
        02 62 03 c9 f8 e6 c0 b0 36

ฉันอาจเพิ่มการทดสอบเพิ่มเติมในภายหลัง

การอ้างอิงและการทดสอบ

เพื่อช่วยในการทดลองของตัวเองนี่คือการดำเนินการอ้างอิงบางส่วน (โดยสิ้นเชิงที่ไม่ใช่กอล์ฟ) - มันสามารถส่งออกข้อมูลการติดตาม (รวมถึงคำแนะนำในการถอดประกอบ) ไปstderrที่

วิธีที่แนะนำในการรับแหล่งที่มา:

git clone https://github.com/zirias/gvm --branch challenge --single-branch --recurse-submodules

หรือสาขาชำระเงินchallengeและทำgit submodule update --init --recursiveโคลนหลังจากเพื่อรับระบบสร้างที่กำหนดเองของฉัน

สร้างเครื่องมือด้วย GNU make (เพียงพิมพ์makeหรือgmakeถ้าอยู่ในระบบของคุณการสร้างเริ่มต้นไม่ใช่ GNU)

การใช้งาน :gvm [-s startpc] [-h] [-t] [-c convfile] [-d] [-x] <initial_ram

  • -s startpc - ตัวนับโปรแกรมเริ่มต้นเริ่มต้นที่ 0
  • -h - อินพุตอยู่ในรูปแบบเลขฐานสิบหก (เป็นอย่างอื่นไบนารี)
  • -t - การดำเนินการติดตามไปที่ stderr
  • -c convfile - แปลง opcodes ตามแผนที่ที่กำหนด convfile
  • -d - ดัมพ์หน่วยความจำที่ได้นั้นเป็นข้อมูลไบนารี่
  • -x - ถ่ายโอนข้อมูลหน่วยความจำที่เกิดเป็น hex
  • initial_ram - เนื้อหา RAM เริ่มต้นไม่ว่าจะเป็นฐานสิบหกหรือไบนารี

หมายเหตุคุณลักษณะการแปลงจะล้มเหลวในโปรแกรมที่ปรับเปลี่ยน opcodes ในขณะที่ทำงาน

คำเตือน:กฎและรายละเอียดด้านบนมีอำนาจสำหรับความท้าทายไม่ใช่เครื่องมือนี้ โดยเฉพาะอย่างยิ่งนี้ใช้กับคุณสมบัติการแปลง opcode หากคุณคิดว่าเครื่องมือที่นำเสนอที่นี่มีข้อผิดพลาด wrt รายละเอียดโปรดรายงานในความคิดเห็น :)


1
ฉันคิดว่าคงมีโอกาสที่จะได้เล่นกอล์ฟมากมายด้วยการเลือก opcodes ที่แตกต่างกันสำหรับคำแนะนำ แต่ดูเหมือนว่า opcodes ได้รับการแก้ไขแล้ว (แม้ว่าชุดคำสั่งจะเป็นตัวกำหนดเครื่อง) อาจจะคุ้มค่าหากพิจารณาว่าการอนุญาตให้การใช้งานมีหน้ารหัสของตัวเอง
Jonathan Allan

1
@JanathanAllan คิดเกี่ยวกับมันสองครั้งตอนนี้ฉันอนุญาตแล้วและอาจเพิ่มเครื่องมือ "การแปลง" เพื่อสร้างโซลูชันโดยใช้ opcodes ชุดอื่น ๆ ทดสอบได้อย่างง่ายดาย
เฟลิกซ์ Palmen

1
@Arnauld ครับเหตุผลของฉันสำหรับการอนุญาตนี้เพื่อที่จะลดปริมาณของกรณีพิเศษดังนั้นจึงควรจะดีกว่า "golfable" - แต่ละ opcode เป็นทั้งนัยญาติสาขาหรือช่วยให้ทั้งสามโหมดอื่น ๆ ที่อยู่ :)
เฟลิกซ์ Palmen

1
ถ้าBRA(สาขา "เสมอ") ไม่แนะนำสาขาในการควบคุมการไหลไม่ควรมันจะเรียกว่าJMP?
ngn

1
@ngn BRAมีอยู่ในการออกแบบชิปในภายหลัง (6502 ไม่มีคำสั่งดังกล่าว) เช่น 65C02 และ MC 68000 JMPมีอยู่เช่นกัน ความแตกต่างคือการBRAใช้การกำหนดที่อยู่แบบสัมพันธ์และJMPใช้การกำหนดที่อยู่แบบสัมบูรณ์ ดังนั้นฉันเพิ่งทำตามการออกแบบเหล่านี้ - แน่นอนว่ามันฟังดูไม่สมเหตุสมผลเลย)
Felix Palmen

คำตอบ:


16

C (gcc) , 1381 1338 1255 1073 ไบต์

การปรับปรุงขนาดใหญ่ต้องขอบคุณแมวเพดานและ Rogem

#include<stdio.h>
C*F="%02hhx ";m[256],p,a,x,z,n,c,e;g;*I(){R++p+m;}*A(){R*I()+m;}*X(){R*I()+m+x;}C*Q(){W(printf,m[g],1)exit(a);}C*(*L[])()={I,Q,A,Q,X,Q,Q,Q};l(C*i){R 254/p?*i=*L[m[p]&7]():*Q();}s(i){R 254/p?*L[m[p]&7]()=i:*Q();}q(){p>254?Q():++p;}C*y(){p+=e;}B(U,z)B(V,!z)B(_,n)B(BM,!n)B(BC,c)B(BS,!c)C*(*b[])()={Q,Q,y,Q,U,Q,V,Q,_,Q,BM,Q,BC,Q,BS,Q};j(){z=!l(&a);v}o(){s(a);}t(){z=!l(&x);n=x&H;}D(){s(x);}f(K(&=)h(K(|=)i(K(^=)J(E;c=e&1;z=!(e/=2);s(e);w}k(E;c=w;z=!e;s(e*=2);}T(E;g=e&1;z=!(e=e/2|H*!!c);c=g;s(e);w}M(E;g=w z=!(e=e*2|H*!!c);c=g;s(e);}N(E;z=!(a=g=a+e+!!c);c=g>>8%2;G}P(E;z=!~e;--p;s(g=e+1);G}u(E;g=e-1;z=!g;--p;s(g);G}r(E;g=a-e;z=!g;c=G}S(E;g=x-e;z=!g;c=G}Y(){z=!(x=g=1-m[p]%2*2);n=x&H;}Z(){c=~m[p]&1;}d(){p<255||Q();e=m[++p];b[m[p-1]&15]();}(*O[])()={j,o,t,D,Q,Q,f,h,i,J,k,T,M,N,Q,P,u,r,S,Q,Q,Q,Q,Q,Q,Y,Z,Q,Q,Q,d,d};main(){scanf(F,&p);W(scanf,&m[g],0)for(;;q())O[m[p]/8]();}

ลองออนไลน์!

มีการกำหนดจำนวนมากที่ย้ายไปยังธงคอมไพเลอร์

คำอธิบาย (ungolfed มาก):

#include<stdio.h>

// useful defines
#define C unsigned char
#define H 128 // highest bit
#define R return

// code generator for I/O
#define W(o,p,q)for(g=-1;++g<256&&((q)||!feof(stdin));)(o)(F,(p));

// code generator for branching instruction handlers
#define BB(q)(){(q)||Y();}

// frequent pieces of code
#define NA n=a&H;
#define NE n=e&H;
#define NG n=g&H;
#define E l(&e)

// printf/scanf template
C * F = "%02hhx ";

// global state: m=memory, pax=registers, znc=flags
// e and g are for temporaries and type conversions
C m[256],p,a,x,z,n,c,e;g;

// get the pointer to a memory location:
C * I() {R &m[++p];} // immediate
C * A() {R &m[m[++p]];} // absolute
C * X() {R &m[m[++p]+x];} // indexed

// terminate the VM (and dump memory contents)
C * Q() { W(printf,m[g],1) exit(a);}

// an array of functions for accessing the memory
// They either return the pointer to memory location
// or terminate the program.
C * (*L[])()={I,Q,A,Q,X,Q,Q,Q};

// load a byte from the memory into the variable pointed by i
// terminate the program if we cannot access the address byte
l (C * i) {return 254 / p ? *i = *L[m[p]&7] () : *Q ();}

// save a byte i to the memory
// terminate the program if we cannot access the address byte
s (C i) {return 254 / p ? *L[m[p]&7]() = i : *Q ();}

// advance the instruction pointer (or fail if we fall outside the memory)
q () {p > 254 ? Q () : ++p;}

// branch
C * Y() {p += e;}

// generated functions for conditional branches
C * BN BB(z)
C * BZ BB(!z)
C * BP BB(n)
C * BM BB(!n)
C * BC BB(c)
C * BS BB(!c)

// a list of branch functions
C * (*B[])() = {Q,Q,Y,Q,BN,Q,BZ,Q,BP,Q,BM,Q,BC,Q,BS,Q};

// Instruction handling functions

OA () {z = !l (&a); NA} // lda
OB () {s (a);} // sta
OC () {z = !l (&x); n = x & H;} // ldx
OD () {s (x);} // stx
OG () {E; z = !(a &= e); NA} // and
OH () {E; z = !(a |= e); NA} // ora
OI () {E; z = !(a ^= e); NA} // eor
OJ () {E; c = e & 1; z = !(e /= 2); s (e); NE} // lsr
OK () {E; c = NE; z = !e; s (e *= 2);} // asl
OL () {E; g = e & 1; z = !(e = e / 2 | H * !!c); c = g; s (e); NE} // ror
OM () {E; g = e & H; z = !(e = e * 2 | H * !!c); c = g; s (e); NE} // rol
ON () {E; z = !(a = g = a + e + !!c); c = !!(g & 256); NG} // adc
OP () {E; z = !~e; --p; s (g = e + 1); NG} // inc
OQ () {E; g = e - 1; z = !g; --p; s (g); NG} // dec
OR () {E; g = a - e; z = !g; c = NG} // cmp
OS () {E; g = x - e; z = !g; c = NG} // cpx
OY () {z = !(x = g = ~m[p] & 1 * 2 - 1); n = x & H;} // inx/dex
OZ () {c = ~m[p] & 1;} // sec/clc
Od () {p < 255 || Q (); e = m[++p]; B[m[p-1]&15] ();} // branching

// list of opcode handlers
(*O[]) () = {OA,OB,OC,OD,Q,Q,OG,OH,OI,OJ,OK,OL,OM,ON,Q,OP,OQ,OR,OS,Q,Q,Q,Q,Q,Q,OY,OZ,Q,Q,Q,Od,Od};

// main function
main ()
{
    // read the instruction pointer
    scanf (F, &p);

    // read memory contents
    W(scanf, &m[g], 0)

    // repeatedly invoke instruction handlers until we eventually terminate
    for (;; q())
        O[m[p]/8] ();
}

ทำได้ดีมาก +1 ทันทีไม่ได้คาดหวังว่าโซลูชัน C จะเป็นคนแรก :) การผนวก00ไบต์ของคุณอาจเป็นไปตามกฎนิดหน่อย แต่ฉันยอมรับว่าฉันไม่ได้พยายามวิเคราะห์รหัสนี้ ... บันทึก bytes ทำ I / O ในไบนารีแทน hex? จะได้รับอนุญาตตามกฎ :)
เฟลิกซ์ Palmen

ฉันต้องการแทนที่กฎที่ opcodes ผิดกฎหมายนำไปสู่การยกเลิกโดยเพียงแค่บอกว่าพฤติกรรมของ opcodes ผิดกฎหมายนั้นไม่ได้กำหนดไว้ ... สิ่งนี้จะทำร้ายคำตอบของคุณหรือคุณพอใจกับสิ่งนั้นหรือไม่
เฟลิกซ์ Palmen

@ FelixPalmen ได้ดีตราบใดที่การเลิกใช้งานค่อนข้างถูกต้อง "undefined" พฤติกรรมมันจะไม่เจ็บ (มันเปิดโอกาสใหม่ให้กับการเล่นกอล์ฟแทน!)
Max Yekhlakov

@ MaxYekhlakov โดย "เจ็บ" ฉันหมายถึงไม่ยุติธรรมต่อการแก้ปัญหาของคุณเพราะคุณอาจจะ "ใช้ไบต์" เพื่อให้แน่ใจว่า opcode ผิดกฎหมายยุติ vm ฉันดีใจที่คุณยินดีต้อนรับการเปลี่ยนแปลงกฎเป็นโอกาส :) และอีกครั้งขอแสดงความยินดีฉันแค่ชอบที่จะเห็นวิธีแก้ปัญหาใน C ซึ่งเป็นภาษาโปรแกรมที่ฉันโปรดปรานตลอดกาล เป็นเรื่องน่าเสียดายที่คุณไม่ค่อยได้รับความท้าทายจากการเล่นกอล์ฟใน C แต่ทว่า "golfed" C นั้นเจ๋งมาก :)
Felix Palmen

คุณสามารถเพิ่มธงเพื่อโพสต์ได้หรือไม่?
l4m2

8

APL (Dyalog คลาสสิก) , 397 332 330 ไบต์

ขอบคุณ @ Adámสำหรับ -8 ไบต์

f←{q2a x c←≡B256⋄{0::m
⍺←(∇q∘←)0∘=,B≤+⍨
30u←⌊8÷⍨bpm:∇p+←129-B|127-1pm×⊃b2/(,~,⍪)1,q,c
p+←1
u=25:⍺x⊢←B|x1*b
u=26:∇c⊢←~2|b
p+←≢om⊃⍨i←⍎'p⊃m+x'↑⍨1+8|b
u⊃(,⍉'⍺ax⊢←o' '∇m[i]←ax'∘.~'xa'),5 4 2 3 2/'⍺⌽⊃'∘,¨'a⊢←2⊥(⍕⊃u⌽''∧∨≠'')/o a⊤⍨8⍴2' 'c(i⊃m)←u⌽d⊤(⌽d←u⌽2B)⊥u⌽o,c×u>10' 'c a⊢←2B⊤a+o+c' 'm[i]←B|o-¯1*u' 'c⊢←⊃2B⊤o-⍨⊃u⌽x a'}p m←⍵}

ลองออนไลน์!

p  program counter
m  memory
a  accumulator register
x  index register
q  flags z (zero) and n (negative) as a length-2 vector
c  flag for carry
  function to update z and n
b  current instruction
u  highest 5 bits of b
o  operand
i  target address in memory

ขอให้เรายังคงอภิปรายนี้ในการแชท
ngn

วิธีแก้ปัญหานี้มี opcodes ที่ไม่ได้ตั้งใจหรือไม่และคุณไม่ได้ใช้ไบต์เพื่อหลีกเลี่ยงมันหรือไม่? ดูความคิดเห็นนี้สำหรับเหตุผลที่ฉันขอ ...
เฟลิกซ์ Palmen

@ FelixPalmen ตอนนี้คุณพูดถึงแล้วใช่ :( เริ่มแรกฉันสังเกตเห็นกฎนั้น แต่เมื่อฉันเล่นกอล์ฟฉันบังเอิญทำ 4, 5 และอาจเป็นเพราะคนอื่น ๆ opcodes ที่ถูกต้องดังนั้นการตัดสินใจที่จะทำให้พฤติกรรมของพวกเขาไม่เป็นที่ยอมรับ :)
ngn

2
เสร็จแล้วตอนนี้ฉันรู้ว่ามันไม่ใช่การตัดสินใจที่ดีที่สุดในตอนแรกและ @MaxYekhlakov น่าเสียดายที่ไม่ต้องพูดอะไรเกี่ยวกับการเปลี่ยนแปลงกฎ
เฟลิกซ์ Palmen

คุณต้องการf←ไหม
Erik the Outgolfer

8

C (gcc) , 487 , 480 , 463 , 452 , 447 , 438 ไบต์

ใช้การทำแผนที่คำสั่งนี้ การอัปเดตคำแนะนำจะถูกลบออกไป 9 ไบต์และอาจมีมากขึ้นในอนาคต ส่งคืนโดยการแก้ไขหน่วยความจำที่ชี้ไปตามอาร์กิวเมนต์แรก ( M) ขอขอบคุณ @ceilingcat ที่ช่วยกำจัดบางไบต์

จะต้องรวบรวมด้วยธง-DO=*o -DD=*d -DI(e,a,b)=if(e){a;}else{b;} -Du="unsigned char"(รวมอยู่ในไบต์)

e(M,Z,C)u*M,C;{for(u r[2],S=0,D,O,c,t;o=C<Z?M+C++:0;){I(c=O,d=r+!(c&4),break)I(o=c&3?C<Z&&C?M+C++:0:d,o=c&2?O+c%2**r+M:o,break)t=(c/=8)&7;I(c<24&c>4&&t,t&=3;I(c&8,I(c&4,c&=S&1;S=O>>7*!(t/=2);O=t=O<<!t>>t|c<<7*t,t=O+=t%2*2-1),I(c&4,D=t=t?t&2?t&1?O^D:O|D:O&D:O,I(c&1,S=D>(t=D+=O+S%2),t=D-O;S=t>D)))S=S&1|t>>6&2|4*!t,I(c&8,C+=!(t&~-t?~t&S:t&~S)*O,I(t,S=S&6|c%2,O=D)))I(C,,Z=0)}}

ลองออนไลน์!

preprocessor

-DO=*o -DD=*d

ทั้งสองนี้ให้วิธีที่สั้นกว่าในการตรวจสอบพอยน์เตอร์

-DI(e,a,b)=if(e){a;}else{b;} -Du="unsigned char"

ลดจำนวนไบต์ที่จำเป็นสำหรับการประกาศ if-elses และพิมพ์

รหัส

ด้านล่างเป็นโค้ดเวอร์ชันที่มนุษย์อ่านได้โดยมีคำสั่ง preprocessor ถูกขยายและตัวแปรถูกเปลี่ยนชื่อเพื่อให้อ่านง่าย

exec_8bit(unsigned char *ram, int ramSize, unsigned char PC)
{  
  for(unsigned char reg[2], SR=0, // The registers. 
                                  // reg[0] is X, reg[1] is A. 
                                  // SR contains the flags.
      *dst, *op, opCode, tmp;
      // Load the next instruction as long as we haven't gone out of ram.
      op = PC < ramSize ? ram + PC++ : 0;)
  { // Check for HLT.
    if(opCode=*op)
    { // Take a pointer to the register selected by complement of bit 3.
      dst = reg+!(opCode&4);
    } else break;
    // Load operand as indicated by bits 0 and 1. Also check that we don't
    // go out of bounds and that the PC doesn't overflow.
    if(op = opCode&3 ? PC<ramSize && PC ? ram + PC++ : 0 : dst)
    {
      op = opCode&2 ? *op + opCode%2 * *reg + ram: op
    } else break;

    // Store the bits 3-5 in tmp.
    tmp = (opCode/=8) & 7;
    if(opCode<24 & opCode>4 && tmp)
    { // If not HLT, CLC, SEC or ST, enter this block.
      tmp &= 3; // We only care about bits 3&4 here.
      if(opCode&8) // Determine whether the operation is binary or unary.
      { // Unary
        if(opCode&4)
        { // Bitshift
          opCode &= SR&1; // Extract carry flag and AND it with bit 3 in opCode.
          SR=*op >> 7*!(tmp/=2);// Update carry flag.
          // Shift to left if bit 4 unset, to right if set. Inclusive-OR 
          // the carry/bit 3 onto the correct end.
          *op = tmp = *op << !tmp >> tmp | opCode << 7*tmp;
        } else tmp=*o+=tmp%2*2-1;
      } else if(opCode&4) {
        // Bitwise operations and LD.
        // Ternary conditional to determine which operation.
        *dst = tmp = tmp? tmp&2? tmp&1? *op^*dst: *op|*dst: *op&*dst: *op
      } else if(opCode&1) {
        // ADC. Abuse precedence to do this in fewer bytes.
        // Updates carry flag.
        SR = *dst > (tmp = *dst += *op + SR%2);
      } else tmp=*dst-*op; SR = tmp > *dst; // Comparison.
      SR = SR&1 | tmp >> 6&2 | 4*!tmp; // Update Z and N flags, leaving C as it is.
    } else if(opCode&8) {
      // Branch.
      // tmp&~-tmp returns a truthy value when tmp has more than one bit set
      // We use this to detect the "unset" and "always" conditions.
      // Then, we bitwise-AND either ~tmp&SR or tmp&~SR to get a falsy value
      // when the condition is fulfilled. Finally, we take logical complement,
      // and multiply the resulting value (`1` or `0`) with the operand,
      // and add the result to program counter to perform the jump.
      PC += !(tmp & ~-tmp? ~tmp&SR : tmp&~SR) * *op;
    } else if (tmp) { // SEC, CLC
      SR = SR&6 | opCode % 2;
    } else {
      *op = *dst; // ST
    }
    if(!PC){ // If program counter looped around, null out ramSize to stop.
           // There's likely a bug here that will kill the program when it
           // branches back to address 0x00
      ramSize=0;
    }
  }
}

คำแนะนำ

คำแนะนำมีโครงสร้างดังนี้:

  • Bits 6-7 แสดงถึง arity ของคำสั่ง ( 00Nullary, 01Unary, 10Binary, 11Binary)

  • บิต 0-2 ตรวจสอบถูกดำเนินการ (s): R=0เลือกAและเลือกR=1 ใช้การลงทะเบียนเป็นตัวถูกดำเนินการเลือกตัวถูกดำเนินการทันทีเลือกตัวถูกดำเนินการที่แน่นอนและเลือกตัวถูกดำเนินการที่จัดทำดัชนีXOP=00OP=01OP=10OP=11

    • ตามที่คุณอาจสังเกตเห็นสิ่งนี้จะช่วยให้การดำเนินการใด ๆ ที่จะดำเนินการในการลงทะเบียนอย่างใดอย่างหนึ่ง (แม้ว่าคุณจะยังคงสามารถจัดทำดัชนีจากX) แม้ว่าจะไม่สามารถใช้งานได้ตามปกติ เช่นINC A, ADC X, 10และASL Xการทำงานทั้งหมด
  • บิตที่ 3-5 กำหนดเงื่อนไขสำหรับการแยก: มีหนึ่งในบิตที่ระบุว่าการทดสอบการตั้งค่าสถานะใด (บิต 3-> C บิต 4-> N บิต 5> Z) หากตั้งค่าเพียงหนึ่งบิตคำสั่งจะทดสอบการตั้งค่าสถานะ เมื่อต้องการทดสอบการตั้งค่าสถานะ unset ใช้บิตของส่วนประกอบ ตัวอย่างเช่น110การทดสอบสำหรับการไม่ได้พกพาและ001การพกพา 111และ000สาขาเสมอ

  • คุณยังสามารถแยกสาขาไปยังที่อยู่ออฟเซ็ตที่เก็บไว้ในรีจิสเตอร์เพื่อให้คุณสามารถเขียนฟังก์ชั่นหรือคุณสามารถใช้โหมดการจัดทำดัชนีมาตรฐาน OP=01พฤติกรรมเช่นสาขาสเปค

+-----+----------+-------+-----------------------------------------------+
| OP  | BINARY   | FLAGS | INFO                                          |
+-----+----------+-------+-----------------------------------------------+
| ST  | 10000ROP |       | Register -> Operand                           |
| LD  | 10100ROP | Z N   | Operand -> Register                           |
| AND | 10101ROP | Z N   | Register &= Operand                           |
| XOR | 10111ROP | Z N   | Register ^= Operand                           |
| IOR | 10110ROP | Z N   | Register |= Operand                           |
| ADC | 10011ROP | Z N C | Register += Operand + Carry                   |
| INC | 01011ROP | Z N   | Operand += 1                                  |
| DEC | 01010ROP | Z N   | Operand -= 1                                  |
| ASL | 01100ROP | Z N C | Operand <<= 1                                 |
| LSR | 01110ROP | Z N C | Operand >>= 1                                 |
| ROL | 01101ROP | Z N C | Operand = Operand << 1 | Carry                |
| ROR | 01111ROP | Z N C | Operand = Operand >> 1 | Carry << 7           |
| CMP | 10010ROP | Z N C | Update ZNC based on Register - Operand        |
| BR  | 11CNDROP |       | PC += Condition ? Operand : 0      |
| SEC | 00011000 |     C | Set carry                                     |
| CLC | 00010000 |     C | Clear carry                                   |
| HLT | 00000000 |       | Halt execution.                               |
+-----+----------+-------+-----------------------------------------------+

7

JavaScript (ES6), 361 ไบต์

รับอินพุตเป็น(memory)(program_counter), ที่ไหนmemoryคือUint8Arrayเป็นเอาต์พุตโดยการแก้ไขอาร์เรย์นี้

M=>p=>{for(_='M[\u0011\u0011A]\u0010\u0010=\u000fc=\u000e,\u0011p]\f(n=\u000b128)\t=\u0010\b&=255\u0007,z=!(n\u0007),n&=\t;\u0006\u0006\u000b\u0005-\u0010,\u000en>=0\u0005\u0004\u0011c\b>>7,A]*2\u0005\u0003\u0011c\b&1,A]/2\u0005\u000f\u0002&&(p+=(\u0010^\t-\t;\u0001for(a=n=z=\u000ex=0;a\u0007,x\u0007,A=[i=\u0011p++],p\f\f+x][i&3],i&3&&p++,i&&A<256;)eval(`\u000ba\b\u0006\u000fa;\u000bx\b\u0006\u000fx;\u000ba&\b\u0005a|\b\u0005a^\b\u0005\u000f\u0002\u0003\u000fc*128|\u0002c|\u0003a+\b+c,\u000ea>>8\u0005++\u0010\u0005--\u0010\u0005a\u0004x\u0004++x\u0005--x\u0006\u000e1;\u000e0;1\u0001!z\u0001z\u0001!n\u0001n\u0001!c\u0001c\u0001`.split`;`[i>>2])';G=/[\u0001-\u0011]/.exec(_);)with(_.split(G))_=join(shift());eval(_)}

หมายเหตุ: รหัสถูกบีบอัดด้วยRegPackและมีอักขระที่ไม่สามารถพิมพ์ได้จำนวนมากซึ่งทั้งหมดจะถูกหลบหนีในการแสดงแหล่งที่มาด้านบน

ลองออนไลน์!

การทำแผนที่ Opcode และกรณีทดสอบ

เครื่องเสมือนใช้ การทำแผนที่ opcodeนี้

ด้านล่างนี้เป็นกรณีทดสอบที่แปลแล้วพร้อมกับผลลัพธ์ที่คาดหวัง

กรณีทดสอบ # 1

00 - LDX #$10  09 10
02 - INC $01   32 01
04 - DEX       44
05 - BNE $fb   55 fb

ผลลัพธ์ที่คาดหวัง:

09 20 32 01 44 55 fb

กรณีทดสอบ # 2

00 - (DATA)    e0 08 2a 02
04 - LDA $00   02 00
06 - ADC $02   2e 02
08 - STA $00   06 00
0a - LDA $01   02 01
0c - ADC $03   2e 03
0e - STA $01   06 01

ผลลัพธ์ที่คาดหวัง:

0a 0b 2a 02 02 00 2e 02 06 00 02 01 2e 03 06 01

กรณีทดสอบ # 3

00 - (DATA)    5e 01 28 00
04 - LDX #$10  09 10
06 - LSR $01   1e 01
08 - ROR $00   26 00
0a - BCC $0d   65 0d
0c - LDA $02   02 02
0e - CLC       4c
0f - ADC $21   2e 21
11 - STA $21   06 21
13 - LDA $03   02 03
15 - ADC $22   2e 22
17 - STA $22   06 22
19 - ASL $02   22 02
1b - ROL $03   2a 03
1d - DEX       44
1e - BPL $e6   5d e6
20 - HLT       00
21 - (DATA)    00 00

ผลลัพธ์ที่คาดหวัง:

00 00 00 00 09 10 1e 01 ... 44 5d e6 00 b0 36

แตกและจัดรูปแบบ

เนื่องจากรหัสถูกบีบอัดด้วยอัลกอริทึมที่แทนที่สตริงซ้ำบ่อยครั้งด้วยอักขระเดียวจึงมีประสิทธิภาพมากกว่าในการใช้โค้ดบล็อกเดียวกันซ้ำไปซ้ำมามากกว่าการกำหนดและเรียกใช้ฟังก์ชันตัวช่วยหรือเก็บผลลัพธ์ระดับกลาง (เช่นM[A]) ในตัวแปรเพิ่มเติม

M => p => {
  for(
    a = n = z = c = x = 0;
    a &= 255, x &= 255,
    A = [i = M[p++], p, M[p], M[p] + x][i & 3],
    i & 3 && p++,
    i && A < 256;
  ) eval((
    '(n = a = M[A], z = !(n &= 255), n &= 128);'                                + // LDA
    'M[A] = a;'                                                                 + // STA
    '(n = x = M[A], z = !(n &= 255), n &= 128);'                                + // LDX
    'M[A] = x;'                                                                 + // STX
    '(n = a &= M[A], z = !(n &= 255), n &= 128);'                               + // AND
    '(n = a |= M[A], z = !(n &= 255), n &= 128);'                               + // ORA
    '(n = a ^= M[A], z = !(n &= 255), n &= 128);'                               + // EOR
    '(n = M[A] = M[c = M[A] & 1, A] / 2, z = !(n &= 255), n &= 128);'           + // LSR
    '(n = M[A] = M[c = M[A] >> 7, A] * 2, z = !(n &= 255), n &= 128);'          + // ASL
    '(n = M[A] = c * 128 | M[c = M[A] & 1, A] / 2, z = !(n &= 255), n &= 128);' + // ROR
    '(n = M[A] = c | M[c = M[A] >> 7, A] * 2, z = !(n &= 255), n &= 128);'      + // ROL
    '(n = a += M[A] + c, c = a >> 8, z = !(n &= 255), n &= 128);'               + // ADC
    '(n = ++M[A], z = !(n &= 255), n &= 128);'                                  + // INC
    '(n = --M[A], z = !(n &= 255), n &= 128);'                                  + // DEC
    '(n = a - M[A], c = n >= 0, z = !(n &= 255), n &= 128);'                    + // CMP
    '(n = x - M[A], c = n >= 0, z = !(n &= 255), n &= 128);'                    + // CPX
    '(n = ++x, z = !(n &= 255), n &= 128);'                                     + // INX
    '(n = --x, z = !(n &= 255), n &= 128);'                                     + // DEX
    'c = 1;'                                                                    + // SEC
    'c = 0;'                                                                    + // CLC
    ' 1 && (p += (M[A] ^ 128) - 128);'                                          + // BRA
    '!z && (p += (M[A] ^ 128) - 128);'                                          + // BNE
    ' z && (p += (M[A] ^ 128) - 128);'                                          + // BEQ
    '!n && (p += (M[A] ^ 128) - 128);'                                          + // BPL
    ' n && (p += (M[A] ^ 128) - 128);'                                          + // BMI
    '!c && (p += (M[A] ^ 128) - 128);'                                          + // BCC
    ' c && (p += (M[A] ^ 128) - 128);')                                           // BCS
    .split`;`[i >> 2]
  )
}

น่าประทับใจ :) ไม่ใช่ JS pro ดังนั้น - การจัดทำดัชนีนี้เป็น "อาร์เรย์รหัส" บางส่วนโดยค่าของ opcode หรือไม่ ดูดี. แต่ถ้าo = ...มีการดำเนินการบรรทัดนี้สำหรับทุกคำสั่งอาจเป็น "opcodes ที่ไม่ได้ตั้งใจ" หรือไม่?
เฟลิกซ์ Palmen

2
ฉันควรจะเพิ่มกรณีทดสอบ: o ตอนนี้ฉันคิดว่ามันจะดีกว่าที่จะอนุญาต opcodes ที่ไม่ได้ตั้งใจ ... การตรวจสอบความถูกต้องเพียงแค่เสียไบต์ที่นี่ แต่อาจสายเกินไปที่จะเปลี่ยนกฎ :(
Felix Palmen

ฉันกำลังจะแนะนำอย่างแน่นอนเพราะมันไม่ได้เพิ่มความท้าทายมากนักและตอนนี้ยากที่จะตรวจสอบกับการจับคู่ที่กำหนดเอง แต่คุณอาจต้องถาม / เตือน @MaxYekhlakov ก่อนเนื่องจากอาจมีการใช้กฎอย่างถูกต้อง
Arnauld

c = M[A] >> 7 & 1<- &1นี่จำเป็นจริงๆหรือ
เฟลิกซ์ Palmen

2
ฉันค่อนข้างแน่ใจว่ามันจะเป็นฟังก์ชั่นของคุณต่อไปถ้อยคำของฉันคือ "รายการของไบต์ [... ] รูปแบบที่เหมาะสม" และUint8Arrayแน่นอนเพียงแค่แค็ปซูลรายการของไบต์ ดังนั้นหากวางไบต์ในสตริงเลขฐานสิบหกเป็นวิธีที่ยอมรับได้ในการเป็นตัวแทนของการป้อนข้อมูลทำไมจึงควรวางไว้ในวัตถุภาชนะเป็นสิ่งต้องห้าม ...
Felix Palmen

2

PHP, 581 585 555 532 ไบต์ (ไม่ใช่ - การแข่งขัน)

function t($v){global$f,$c;$f=8*$c|4&$v>>5|2*!$v;}$m=array_slice($argv,2);for($f=0;$p>-1&&$p<$argc-1&&$i=$m[$p=&$argv[1]];$p++)$i&3?eval(explode(_,'$a=$y_$a&=$y_$a|=$y_$a^=$y_$a+=$y+$c;$c=$a>>8_$x=$y_$c=$y&1;$y>>=1_$c=($y*=2)>>8_$y+=$y+$c;$c=$y>>8_$y+=$c<<8;$c=$y&1;$y>>=1_$y--_$y++_$z=$a-$y,$c=$a<$y_$z=$x-$y,$c=$x<$y_$y=$a_$y=$x_'.$y=&$m[[0,++$p,$g=$m[$p],$g+$x][$i&3]])[$i>>=2].'$i<14&&t(${[aaaaaxyyyyyyzz][$i]}&=255);'):($i&32?$p+=($f>>$i/8-4^$i)&1?($y=$m[++$p])-($y>>7<<8):1:($i&8?$f=$f&7|8*$c=$i/4:t($x+=$i/2-9)));print_r($m);

ใช้เวลารหัส PC และ OP และเป็นฐาน 10 จำนวนเต็มจากอาร์กิวเมนต์บรรทัดคำสั่ง, หน่วยความจำพิมพ์รายชื่อของ
[base 10 address] => base 10 value

นี้จะไม่ได้รับการทดสอบอย่างละเอียดเลย ; แต่มีรายละเอียด

There's แผนที่รหัส และ here's ภาพรวมสำหรับการทำแผนที่ของฉัน:

3-mode instructions:
00: LDA     04: AND     08: ORA     0C: EOR
10: ADC     14: LDX     18: LSR     1C: ASL
20: ROL     24: ROR     28: DEC     2C: INC
30: CMP     34: CPX     38: STA     3C: STX

+1: immediate
+2: absolute
+3: relative

implicit:
00: HLT
10: DEX 14: INX
18: CLC 1C: SEC

relative:
20: BRA         (0)
28: BNE 2C: BEQ (Z)
30: BPL 34: BMI (N)
38: BCC 3C: BCS (C)

หมายเหตุ: ผลลัพธ์ของ
โค้ด24เป็น a BNV(branch never = 2 byte NOP);
04, 08, 0Cเป็นนามแฝงสำหรับINX, CLCและSEC
และสิ่งที่เหนือ3Fเป็นทั้งสองไบต์NOPหรือนามแฝงสำหรับคำแนะนำโหมดเดียว

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