2 คะแนน โดย GN⁺ 2026-02-08 | 1 ความคิดเห็น | แชร์ทาง WhatsApp
  • SectorC ที่เขียนด้วยแอสเซมบลี x86-16 เป็นคอมไพเลอร์ C ขนาดจิ๋วมากที่ใส่ได้ภายใน บูตเซกเตอร์ (512 ไบต์) ของเครื่อง x86 และรองรับ ส่วนย่อยของภาษา C มากพอสำหรับเขียนโปรแกรมที่ทำงานได้จริง
  • รองรับ ตัวแปรโกลบอล, ฟังก์ชัน, คำสั่ง if/while, โอเปอเรเตอร์, การ dereference พอยน์เตอร์, คอมเมนต์, อินไลน์แอสเซมบลี เป็นต้น ทำให้แม้มีโครงสร้างขั้นต่ำก็ยังสามารถรันโปรแกรมที่สมบูรณ์ได้
  • เพื่อทำให้ตัว tokenizer เรียบง่าย จึงออกแบบภาษา Barely C ที่ใช้ การแยกโทเค็นตามช่องว่าง และแฮชด้วย atoi() โดยยังคงไวยากรณ์ C เดิมไว้พร้อมกับลดขนาดได้อย่างสุดขีด
  • ในกระบวนการปรับแต่งโค้ด ได้ใช้เทคนิคบีบอัดระดับแอสเซมบลีหลากหลายแบบ เช่น การตัด jump, การรวม call, การใช้ออฟเซ็ต 8 บิต, การใช้ stosw/lodsw จนลดขนาดจาก 468 ไบต์เหลือ 303 ไบต์
  • ผลลัพธ์คือการสร้างคอมไพเลอร์ C ที่สมบูรณ์ภายใน 512 ไบต์ โดยมีทั้ง tokenizer, parser, code generator และ runtime ครบถ้วน แสดงให้เห็น ตัวอย่างสุดขั้วของการทำซอฟต์แวร์ให้เล็กที่สุด

ภาพรวมของ SectorC

  • SectorC เป็นคอมไพเลอร์ C ที่เขียนด้วย แอสเซมบลี x86-16 และใส่ได้ครบถ้วนภายใน บูตเซกเตอร์ขนาด 512 ไบต์
    • ที่เก็บโค้ด GitHub คือ xorvoid/sectorc
    • ภาษาที่รองรับคือส่วนย่อยของ C ในระดับที่สามารถเขียนโปรแกรมใช้งานจริงได้
  • ความสามารถที่รองรับประกอบด้วย ตัวแปรโกลบอล, ฟังก์ชัน, คำสั่งควบคุม (if/while), โอเปอเรเตอร์หลายแบบ, การ dereference พอยน์เตอร์, อินไลน์แอสเซมบลี, คอมเมนต์
  • มีการยกตัวอย่างโปรแกรม โค้ดที่วาดแอนิเมชันคลื่นไซน์ในโหมด VGA

ที่มาของการออกแบบและแนวทาง

  • เนื่องจาก tokenizer ของภาษา C แบบเดิมมีขนาดใหญ่เกินกว่าจะใส่ใน 512 ไบต์ได้ จึงต้อง ทำให้โครงสร้างภาษาง่ายลงตั้งแต่ต้น
  • Big Insight #1: นำโครงสร้างโทเค็นที่ คั่นด้วยช่องว่าง แบบภาษา Forth มาใช้ และออกแบบภาษาแปลงรูปชื่อ “Barely C”
    • ตัวอย่าง: ไวยากรณ์อย่าง int(main)(){while(!done){ ถูกจัดการเป็น “เมกาโทเค็น” เดียว
    • และยังคงถูกมองว่าเป็นโค้ด C ที่ถูกต้องโดยคอมไพเลอร์ C ปกติ
  • Big Insight #2: ใช้ฟังก์ชัน atoi() เสมือนเป็น ฟังก์ชันแฮช เพื่อแปลงโทเค็นเป็นตัวเลข
    • ทั้งลิเทอรัลจำนวนเต็ม, คีย์เวิร์ด และ identifier ล้วนถูกจัดการผ่านผลลัพธ์ของ atoi()
    • ส่วน identifier เข้าถึงผ่านดัชนีของอาร์เรย์ขนาด 64K

การทำ Barely C ให้ใช้งานได้จริง

  • เวอร์ชันแรกมีขนาด 468 ไบต์ ใช้โครงสร้าง recursive descent parser ที่อิงโทเค็นแบบ atoi
    • ไม่ใช้ symbol table แต่เข้าถึงเซกเมนต์ 64K โดยตรงผ่านค่าแฮช
    • การสร้างโค้ดใช้แนวทางแบบ OTCC โดยใช้รีจิสเตอร์ ax เป็นที่เก็บผลลัพธ์
  • ต่อมามีการทดลองใช้ byte-threaded code เพื่อทำโครงสร้างแบบ Forth แต่พบว่าในข้อจำกัด 512 ไบต์กลับไม่มีประสิทธิภาพ จึงยกเลิกแนวทางนี้

เทคนิคการย่อโค้ด

  • กลับไปใช้โครงสร้างแบบตรงไปตรงมาและลดขนาดจาก 468 ไบต์ → 303 ไบต์
    • ใช้เทคนิคอย่าง การตัด jump (fall-through), tail-call, การรวมการเรียกใช้ (call fusion), การใช้ stosw/lodsw, และ การคงออฟเซ็ต jump แบบ 8 บิต
  • ทำให้มีพื้นที่ว่างเพิ่มขึ้น 207 ไบต์สำหรับใส่ความสามารถเพิ่มเติม

ขยายสู่ความสามารถ C ที่สมบูรณ์

  • ใช้อีก 200 ไบต์เพื่อรองรับ ไวยากรณ์ C ที่สมบูรณ์ยิ่งขึ้น
    • บล็อก if/while แบบซ้อนกัน, โอเปอเรเตอร์แบบไบนารี หลากหลาย (+, -, *, &, |, ^, <<, >>, ==, !=, <, >, <=, >=)
    • รองรับ การประกาศฟังก์ชันและการเรียกแบบรีเคอร์ซีฟ, อินไลน์แอสเซมบลี (asm), และ คอมเมนต์แบบบรรทัดเดียว/หลายบรรทัด
    • ผ่านตารางโอเปอเรเตอร์ (binary_oper_tbl) ที่นิยามโอเปอเรเตอร์แต่ละตัวด้วย 4 ไบต์ ทำให้รองรับโอเปอเรเตอร์ 14 ตัวใน 56 ไบต์

โครงสร้างไวยากรณ์

  • ไวยากรณ์ทั้งหมดประกอบด้วย program, var_decl, func_decl, statement, expr เป็นต้น
  • รองรับคอมเมนต์ทั้ง // และ /* */
  • ตัวข้อความนิยามไวยากรณ์เองมีขนาด 704 ไบต์ ซึ่งใหญ่กว่าตัวอิมพลีเมนเทชันจริงเสียอีก

อินไลน์แอสเซมบลีและรันไทม์

  • สามารถแทรก machine code ของ x86-16 ได้โดยตรง ผ่านคำสั่ง asm
    • เป็นความสามารถที่จำเป็นสำหรับการจัดการ I/O
  • รันไทม์ (rt/) ประกอบด้วยสองไฟล์ที่เขียนด้วย C
    • rt/lib.c: ไลบรารีรูทีนที่อิงอินไลน์แอสเซมบลี
    • rt/_start.c: จุดเริ่มต้นโปรแกรม _start()

โปรแกรมตัวอย่าง

  • examples/hello.c: แสดงข้อความโดยเขียนลงหน่วยความจำ 0xB8000 โดยตรง
  • examples/sinwave.c: แสดงแอนิเมชันคลื่นไซน์ในโหมด VGA 0x13
  • examples/twinkle.c: เล่นเพลง “Twinkle Twinkle Little Star” ผ่านลำโพง PC (มีคำเตือนเรื่องเสียง)

บทสรุป

  • SectorC คือ คอมไพเลอร์ C ขนาดจิ๋วที่ทำเป้าหมายซึ่งดูเป็นไปไม่ได้ให้เกิดขึ้นจริง
    และแสดงให้เห็น ตัวอย่างสุดขั้วของการย่อซอฟต์แวร์และการออกแบบภาษาอย่างสร้างสรรค์
  • ตอนท้ายของบทความปิดด้วยตัวเลือกเชิงขำขันในหัวข้อ “เราเรียนรู้อะไร”
    เพื่อเสียดสีและเน้นย้ำ ทัศนคติของการท้าทายสิ่งที่เป็นไปไม่ได้และคุณค่าของการทำซอฟต์แวร์ให้เรียบง่าย

1 ความคิดเห็น

 
GN⁺ 2026-02-08
ความคิดเห็นจาก Hacker News
  • ถ้ามีการอิมพลีเมนต์แบบนี้อยู่ในยุค 1980 มาตรฐาน C ก็คงจะมีข้อกำหนดว่า ถ้าโทเคนต่างกันทำให้เกิด hash collision กับค่า 16 บิตเดียวกัน จะก่อให้เกิดพฤติกรรมไม่กำหนด (UB) คอมไพเลอร์ที่เน้นการ optimize ในยุค 2000 อาจจะ optimize โทเคนแบบนั้นทิ้งเป็นแค่ no-op ไปเลยก็ได้ 😉
    • มีคนแซวขำ ๆ ว่า “ก็คุณไม่ได้เปิดออปชัน -wTokenHashCollision เองนี่! UB ที่เกิดขึ้นเป็นเพราะความไม่รู้ของคุณนะ สเปกเขียนชัดเจนสมบูรณ์อยู่แล้ว!”
    • สมจริงเกินไปจนหลุดขำเลย LMAO
  • ดูคล้ายกับ X86-16 boot sector C compiler ที่ผมเพิ่งทำเมื่อไม่นานนี้ การทำเกม boot sector เป็นประสบการณ์ที่ทั้งชวนคิดถึงและเหมือนมีมนตร์จริง ๆ ทำให้นึกถึงช่วงเวลาที่การเขียนโปรแกรมยัง สนุกจริง ๆ น่าเสียดายที่ในยุค AI ตอนนี้ โปรเจ็กต์แบบนี้กลับถูกประเมินค่าต่ำเกินไป ลิงก์โปรเจ็กต์ของผม
    • อืม สิ่งที่บทความนี้พูดถึงไม่ใช่ C แบบสมบูรณ์ แต่เป็น คอมไพเลอร์ภาษาคล้าย C ที่ยัดลงใน 512 ไบต์ได้ ดูเหมือนโปรเจ็กต์ของคุณจะมีออปชันสำหรับสร้างโค้ดให้ boot sector ได้ ทั้งคู่ก็น่าสนใจ แต่จุดร่วมมีแค่ประมาณ ‘boot sector’, ‘C’, ‘compiler’ เท่านั้น
    • เห็นด้วยกับคำว่า “ช่วงเวลาที่การเขียนโปรแกรมยังสนุกจริง ๆ” แต่ตอนนี้คนจำนวนมากขึ้นสามารถทำสิ่งเดียวกันได้แล้ว เลยทำให้รู้สึกว่าตัวเองไม่ได้พิเศษอีกต่อไป
  • โปรเจ็กต์แบบนี้เตือนให้เห็นว่าการพัฒนาสมัยใหม่ ห่างจากฮาร์ดแวร์ไปแค่ไหนแล้ว เราสร้างชั้น abstraction ทับกันไปเรื่อย ๆ จนแค่จะรัน “Hello World” ยังต้องใช้ node_modules 200MB แต่ในขณะเดียวกัน ก็มีคนยัด C compiler ลงใน 512 ไบต์ได้ ไม่ได้หมายความว่าทุกคนควรต้องเขียนโค้ด boot sector แต่การได้อ่านโปรเจ็กต์แบบนี้ทำให้ ถ่อมตัวลงมาก และยังมีคุณค่าทางการศึกษาสูงด้วย
  • ผมอาจจะเป็น ผู้เขียน โปรเจ็กต์นี้เองก็ได้ สนุกมากตอนทำมันขึ้นมา!
    • เจ๋งมาก ผมเองก็กำลังทำคอมไพเลอร์ C แบบมินิมัลอยู่เหมือนกัน แต่ไม่ได้เล็งให้ลง boot sector แค่ตั้งเป้าไปที่ ระบบ 8 บิต โปรเจ็กต์นี้แสดงให้เห็นชัดว่า โครงสร้างแกนหลักของ C นั้นเรียบง่ายแค่ไหน น่าสนใจที่ C พัฒนามาจากภาษา B และ B เองก็มาจาก Fortran ฉบับย่อส่วนอีกที
    • สงสัยว่าถ้าแปลง if, while, for ให้เป็นรูทีน goto แบบง่าย ๆ ทั้งหมด จะย่อให้เล็กลงได้อีกแค่ไหน เพราะสุดท้ายใน assembly ก็มีแค่ jmp อยู่ดี แล้วก็ควรเขียนว่า “chose your own adventure” ไม่ใช่ “choose your own adventure” นะ :) ผมรัก minimalism
    • คอมไพเลอร์นี้หรือแนวคิด ‘barely-C’ น่าจะเอาไปใช้ใน ห่วงโซ่การ bootstrap ได้เหมือนกัน คือเริ่มจากไบนารีขนาดจิ๋วที่เจาะจงแพลตฟอร์มมาก ๆ แล้วค่อย ๆ สร้างเครื่องมือและคอมไพเลอร์ที่ซับซ้อนขึ้น ตัวอย่างดูได้จากโปรเจ็กต์ mishmashvm และ tcc_bootstrap_alt
  • เป็นโปรเจ็กต์ที่สวยงามมาก แต่อยากแนะนำว่าเพิ่ม 2023 เข้าไปในชื่อให้ไวหน่อยก็ดี ตอนนั้นมีการคุยกันไว้ ที่นี่
    • ขอบคุณ! ถ้าเป็นเวอร์ชันที่ขยายด้วยแมโคร อาจจัดเป็น SectorC: A C Compiler in 512 bytes - ลิงก์ - พฤษภาคม 2023 (ความคิดเห็น 80 รายการ)
  • น่าสนใจดีถ้าเทียบกับคอมไพเลอร์ C ขนาด 100,000 บรรทัดที่ Claude ทำด้วยเงิน 20,000 ดอลลาร์ในเวลา 2 สัปดาห์ ซึ่งขึ้น HN เมื่อวาน
    • เป็นการเทียบที่สนุกดี แต่ของฝั่งนั้นสามารถคอมไพล์ Linux kernel และสร้างโค้ดได้หลายสถาปัตยกรรม ขณะที่โปรเจ็กต์นี้รองรับได้เพียงบางส่วนของ C พูดอีกอย่างคือ นี่ไม่ใช่ C compiler แบบสมบูรณ์ แต่เป็นคอมไพเลอร์สำหรับ subset ของ C โค้ดที่คอมไพล์ได้ด้วยคอมไพเลอร์นี้ จะคอมไพล์ด้วย C compiler จริงได้ด้วย แต่กลับกันไม่จริง
  • วิธีใช้ token hashing เพื่อสร้าง pseudo symbol table นั้นสวยงามมาก
    • เมื่อก่อนผมก็เคยทำ parser สำหรับเครื่องมือทดสอบ โดยจัดการ identifier ด้วย ค่า hash ของ symbol อย่างเดียว ขอให้ตอนนั้นผมไม่ได้ใช้ symbol มากพอจนชนกันในระดับ 32 บิตก็แล้วกัน
    • ผมก็คิดเหมือนกัน กลเม็ด hashing token นี้เจ๋งมาก เพิ่มเติมคือ ช่วง 0x01e0~0x01fd ยังเหลือ พื้นที่ว่าง 21 ไบต์ อยู่ อาจยัดอะไรเพิ่มได้อีก ;)
  • ตรงที่บอกว่า “atoi() ทำตัวเหมือนฟังก์ชัน hash ที่แย่สำหรับข้อความทั่วไป” นี่น่าสนใจดี แต่ถ้าจำไม่ผิด atoi() ถูกกำหนดไว้ว่า จะคืนค่า 0 เมื่อรับอินพุตที่ไม่ถูกต้อง
  • ดูเหมือนจะมี ความสนใจในคอมไพเลอร์สำหรับ boot sector อยู่พอสมควร ถ้ารันบน Linux ก็แค่เปลี่ยนการเรียก qemu จาก coreaudio เป็น alsa ผมส่ง pull request ไว้บน GitHub แล้ว ถ้าผู้เขียนโอเคกับสไตล์ shell script แบบ verbose ของผม ก็คงอาจจะ merge
  • เป็นบทความที่ยอดเยี่ยมมาก! ทำให้นึกถึง boot sector OS ที่ผมเคยทำไว้ ดูเหมือนถึงเวลาที่จะเพิ่ม C compiler เข้าไปแล้ว ลิงก์โปรเจ็กต์ OS ของผม