2 điểm bởi GN⁺ 2026-02-08 | 1 bình luận | Chia sẻ qua WhatsApp
  • SectorC, được viết bằng hợp ngữ x86-16, là một trình biên dịch C siêu nhỏ có thể nằm gọn trong boot sector (512 byte) của máy x86, và hỗ trợ một tập con của ngôn ngữ C đủ để viết các chương trình thực sự có thể chạy được
  • Nó bao gồm biến toàn cục, hàm, câu lệnh if/while, toán tử, giải tham chiếu con trỏ, chú thích, hợp ngữ nội tuyến, cho phép thực thi các chương trình hoàn chỉnh chỉ với cấu trúc tối thiểu
  • Để đơn giản hóa tokenizer, tác giả đã thiết kế ngôn ngữ Barely C với token hóa dựa trên khoảng trắng và hàm băm dùng atoi(), qua đó vẫn giữ được cú pháp C hiện có đồng thời đạt mức cắt giảm kích thước cực đoan
  • Trong quá trình tối ưu mã, nhiều kỹ thuật nén ở cấp hợp ngữ đã được áp dụng như loại bỏ jump, hợp nhất lời gọi, tận dụng offset 8 bit, sử dụng stosw/lodsw, giúp giảm từ 468 byte xuống 303 byte
  • Kết quả là một trình biên dịch C hoàn chỉnh bao gồm tokenizer, parser, bộ sinh mã và runtime đều nằm trong 512 byte, cho thấy một ví dụ cực hạn về việc tối giản phần mềm

Tổng quan về SectorC

  • SectorC là một trình biên dịch C được viết bằng hợp ngữ x86-16, nằm trọn vẹn trong boot sector 512 byte
    • Kho lưu trữ GitHub: xorvoid/sectorc
    • Ngôn ngữ được hỗ trợ là một tập con của C ở mức đủ để viết chương trình thực tế
  • Các tính năng được hỗ trợ bao gồm biến toàn cục, hàm, câu lệnh điều khiển (if/while), nhiều toán tử, giải tham chiếu con trỏ, hợp ngữ nội tuyến, chú thích
  • Một chương trình ví dụ được đưa ra là mã vẽ hoạt ảnh sóng sin trong chế độ VGA

Bối cảnh thiết kế và cách tiếp cận

  • Vì tokenizer C truyền thống quá lớn để nhét vào 512 byte, tác giả buộc phải đơn giản hóa chính cấu trúc ngôn ngữ
  • Ý tưởng lớn #1: áp dụng cấu trúc token phân tách bằng khoảng trắng giống ngôn ngữ Forth để thiết kế một biến thể tên là “Barely C”
    • Ví dụ: int(main)(){while(!done){ được xử lý như một “mega-token”
    • Trong các trình biên dịch C hiện có, nó vẫn được nhận là mã C hợp lệ
  • Ý tưởng lớn #2: dùng hàm atoi() như một hàm băm để chuyển token thành số
    • Số nguyên literal, từ khóa và định danh đều được xử lý thông qua kết quả của atoi()
    • Định danh được truy cập như chỉ mục của một mảng 64K

Triển khai Barely C

  • Bản triển khai đầu tiên có kích thước 468 byte, với cấu trúc parser đệ quy xuống dùng token dựa trên atoi
    • Không có bảng ký hiệu, mà truy cập trực tiếp segment 64K bằng giá trị băm
    • Bộ sinh mã dùng phong cách OTCC, với thanh ghi ax làm nơi lưu kết quả
  • Sau đó tác giả thử nghiệm byte-threaded code để áp dụng cấu trúc kiểu Forth, nhưng trong giới hạn 512 byte thì cách này lại kém hiệu quả hơn nên bị loại bỏ

Kỹ thuật tối giản mã

  • Quay lại cấu trúc tuyến tính, kích thước được giảm từ 468 byte → 303 byte
    • Sử dụng các kỹ thuật như loại bỏ jump (fall-through), tail-call, hợp nhất lời gọi (call fusion), tận dụng stosw/lodsw, duy trì offset jump 8 bit
  • Giải phóng được 207 byte không gian trống để bổ sung tính năng

Mở rộng thành tính năng C hoàn chỉnh

  • Thêm 200 byte để đạt được hỗ trợ cú pháp C hoàn chỉnh
    • Các khối if/while lồng nhau, nhiều *toán tử nhị phân (+, -, , &, |, ^, <<, >>, ==, !=, <, >, <=, >=)
    • Hỗ trợ định nghĩa hàm và gọi đệ quy, hợp ngữ nội tuyến (asm), chú thích một dòng/nhiều dòng
    • Thông qua bảng toán tử (binary_oper_tbl), mỗi toán tử được định nghĩa trong 4 byte, xử lý 14 toán tử chỉ với 56 byte

Cấu trúc cú pháp

  • Toàn bộ ngữ pháp gồm program, var_decl, func_decl, statement, expr v.v.
  • Hỗ trợ cả chú thích ///* */
  • Phần văn bản định nghĩa ngữ pháp tự nó đã dài 704 byte, còn lớn hơn cả phần triển khai thực tế

Hợp ngữ nội tuyến và runtime

  • Thông qua cú pháp asm, có thể chèn trực tiếp mã máy x86-16
    • Đây là tính năng thiết yếu để xử lý I/O
  • Runtime (rt/) gồm hai tệp được viết bằng C
    • rt/lib.c: các routine thư viện dựa trên hợp ngữ nội tuyến
    • rt/_start.c: điểm vào chương trình _start()

Chương trình ví dụ

  • examples/hello.c: in trực tiếp văn bản vào bộ nhớ 0xB8000
  • examples/sinwave.c: hiển thị hoạt ảnh sóng sin ở chế độ VGA 0x13
  • examples/twinkle.c: phát “Twinkle Twinkle Little Star” qua loa PC (có cảnh báo âm thanh)

Kết luận

  • SectorC là một trình biên dịch C siêu nhỏ hiện thực hóa một mục tiêu tưởng như bất khả thi, đồng thời cho thấy một ví dụ cực hạn về tối giản phần mềm và thiết kế ngôn ngữ đầy sáng tạo
  • Phần cuối bài viết khép lại bằng các lựa chọn hài hước kiểu “bạn đã học được gì”, nhấn mạnh theo lối châm biếm về tinh thần thách thức điều tưởng như không thể và giá trị của việc đơn giản hóa phần mềm

1 bình luận

 
GN⁺ 2026-02-08
Ý kiến trên Hacker News
  • Nếu kiểu triển khai này tồn tại vào những năm 1980, có lẽ tiêu chuẩn C đã có thêm quy tắc rằng nếu các token khác nhau gây ra va chạm băm thành cùng một giá trị 16-bit thì sẽ dẫn đến hành vi không xác định (UB)
    Các trình biên dịch tối ưu hóa của những năm 2000 thậm chí có thể đã tối ưu những token đó thành no-op 😉
    • “Cậu đâu có bật tùy chọn -wTokenHashCollision! UB xảy ra là do cậu thiếu hiểu biết đấy. Đặc tả đã hoàn toàn rõ ràng rồi!” — một phản hồi đùa cợt như vậy
    • Quá giống thực tế nên bật cười luôn. LMAO
  • Trông khá giống với trình biên dịch C boot sector X86-16 mà tôi mới làm gần đây
    Làm game boot sector thật sự là một trải nghiệm đầy ma thuật và gợi hoài niệm. Nó làm tôi nhớ về thời lập trình còn thực sự vui
    Trong thời đại AI bây giờ, thật tiếc khi những dự án như thế này bị đánh giá thấp quá nhiều
    Link dự án của tôi
    • Ừm, thứ bài viết nói tới không phải C hoàn chỉnh, mà là một trình biên dịch ngôn ngữ giống C vừa trong 512 byte
      Dự án của bạn có vẻ có một tùy chọn để sinh mã cho boot sector.
      Cả hai đều thú vị, nhưng điểm chung chỉ cỡ ‘boot sector’, ‘C’, ‘compiler’ mà thôi
    • Tôi đồng cảm với câu “thời lập trình còn thực sự vui”, nhưng giờ đây nhiều người hơn có thể làm được những điều như vậy
      Nên tôi có cảm giác mình không còn đặc biệt nữa
  • Những dự án như thế này nhắc ta nhớ rằng phát triển hiện đại đã xa rời phần cứng đến mức nào
    Chúng ta cứ chồng thêm các lớp trừu tượng, đến mức chỉ để chạy “Hello World” cũng cần 200MB node_modules
    Trong khi đó lại có người nhét được một trình biên dịch C vào 512 byte
    Không phải ai cũng nên viết mã boot sector, nhưng đọc những dự án như thế này thật sự là một trải nghiệm khiến mình khiêm tốn hơn. Giá trị giáo dục của nó cũng rất lớn
  • Có thể tôi chính là tác giả của dự án này. Quá trình làm nó thật sự rất vui!
    • Thật sự rất tuyệt. Tôi cũng đang làm một trình biên dịch C tối giản, nhưng thay vì nhắm vào boot sector thì tôi nhắm tới hệ thống 8-bit
      Dự án này cho thấy cấu trúc cốt lõi của C đơn giản đến mức nào.
      Thật thú vị khi C phát triển từ ngôn ngữ B, và B lại phát triển từ một phiên bản rút gọn của Fortran
    • Tôi tự hỏi nếu thay if, while, for bằng các routine goto đơn giản thì nó sẽ còn nhỏ hơn bao nhiêu
      Suy cho cùng trong assembly cũng chỉ có jmp thôi mà.
      Và nên viết là “chose your own adventure” chứ không phải “choose your own adventure” :)
      Tôi yêu chủ nghĩa tối giản
    • Trình biên dịch này, hay khái niệm ‘barely-C’, có vẻ cũng có thể dùng trong một chuỗi bootstrap
      Kiểu bắt đầu từ một binary rất nhỏ dành riêng cho nền tảng rồi dần dần tạo ra các công cụ và trình biên dịch phức tạp hơn
      Có thể tham khảo các dự án mishmashvmtcc_bootstrap_alt làm ví dụ
  • Một dự án đẹp. Nhưng có lẽ nên nhanh chóng thêm 2023 vào tiêu đề
    Cuộc thảo luận khi đó ở đây
    • Cảm ơn! Với phiên bản có mở rộng macro thì có thể ghi như sau
      SectorC: A C Compiler in 512 bytes - link - tháng 5 năm 2023 (80 bình luận)
  • So với trình biên dịch C dài 100 nghìn dòng mà Claude làm với 20.000 đô trong 2 tuần, được đăng trên HN hôm qua, thì đây là một so sánh khá thú vị
    • So sánh thì vui đấy, nhưng bên kia có thể biên dịch nhân Linux và sinh mã cho nhiều kiến trúc
      Còn dự án này chỉ xử lý được một phần của C
      Nói cách khác, đây không hẳn là một trình biên dịch C đầy đủ mà là trình biên dịch cho một tập con của C.
      Mã có thể biên dịch bằng trình biên dịch này thì cũng biên dịch được bằng trình biên dịch C thật, nhưng chiều ngược lại thì không
  • Cách dùng băm token để tạo một bảng ký hiệu giả thật sự rất thanh lịch
    • Trước đây khi làm parser cho công cụ kiểm thử, tôi cũng từng quản lý định danh chỉ bằng giá trị băm ký hiệu
      May là chắc tôi đã không dùng nhiều ký hiệu đến mức gặp va chạm 32-bit
    • Tôi cũng nghĩ vậy. Mẹo băm token thật sự rất hay
      Nhân tiện, vẫn còn 21 byte chỗ trống trong khoảng 0x01e0~0x01fd. Có khi còn nhét thêm được gì đó ;)
  • Đoạn nói rằng “atoi() hoạt động như một hàm băm tệ cho văn bản thông thường” khá thú vị
    Nhưng nếu tôi nhớ không nhầm thì atoi() được định nghĩa là trả về 0 với đầu vào không hợp lệ
  • khá nhiều quan tâm tới trình biên dịch cho boot sector
    Khi chạy trên Linux thì chỉ cần đổi lệnh gọi qemu từ coreaudio sang alsa
    Tôi đã gửi một pull request lên GitHub cho việc này. Nếu tác giả thấy ổn với phong cách shell script verbose của tôi thì có thể sẽ merge
  • Bài viết thật sự rất hay! Nó làm tôi nhớ tới boot sector OS mà trước đây tôi từng làm
    Có lẽ giờ là lúc thử thêm một trình biên dịch C vào đó
    Link dự án OS của tôi