512바이트로 만든 C 컴파일러 SectorC
(xorvoid.com)- x86-16 어셈블리로 작성된 SectorC는 x86 머신의 부트 섹터(512바이트) 안에 들어가는 초소형 C 컴파일러로, 실제 동작 가능한 프로그램을 작성할 수 있을 만큼의 C 언어 부분집합을 지원함
- 전역 변수, 함수, if/while 문, 연산자, 포인터 역참조, 주석, 인라인 어셈블리 등을 포함해, 최소한의 구조로도 완전한 프로그램 실행이 가능함
- 토크나이저를 단순화하기 위해 공백 기반 토큰화와
atoi()해시를 이용한 Barely C 언어를 설계, 기존 C 문법을 유지하면서도 극단적인 크기 절감을 달성함 - 코드 최적화 과정에서 점프 제거, 호출 병합, 8비트 오프셋 활용,
stosw/lodsw사용 등 다양한 어셈블리 수준의 압축 기법을 적용해 468바이트에서 303바이트로 축소함 - 결과적으로 512바이트 안에서 토크나이저, 파서, 코드 생성기, 런타임을 모두 포함한 완전한 C 컴파일러를 구현, 소프트웨어 최소화의 극단적 사례를 보여줌
SectorC 개요
- SectorC는 x86-16 어셈블리로 작성된 C 컴파일러로, 512바이트 부트 섹터 안에 완전히 들어감
- GitHub 저장소는 xorvoid/sectorc
- 지원 언어는 실제 프로그램 작성이 가능한 수준의 C 부분집합
- 지원 기능에는 전역 변수, 함수, 제어문(if/while), 다양한 연산자, 포인터 역참조, 인라인 어셈블리, 주석 등이 포함됨
- 예시 프로그램으로 VGA 모드에서 사인파 애니메이션을 그리는 코드가 제시됨
설계 배경과 접근 방식
- 기존 C 토크나이저는 512바이트로는 불가능할 정도로 크기 때문에, 언어 구조 자체를 단순화해야 했음
-
Big Insight #1: Forth 언어처럼 공백으로 구분된 토큰 구조를 도입해 “Barely C”라는 변형 언어를 설계
- 예:
int(main)(){while(!done){같은 구문을 하나의 “메가 토큰”으로 처리 - 기존 C 컴파일러에서도 여전히 유효한 C 코드로 인식됨
- 예:
-
Big Insight #2:
atoi()함수를 해시 함수처럼 사용해 토큰을 숫자로 변환- 정수 리터럴, 키워드, 식별자를 모두
atoi()결과값으로 처리 - 식별자는 64K 배열의 인덱스로 접근
- 정수 리터럴, 키워드, 식별자를 모두
Barely C 구현
- 첫 구현은 468바이트로,
atoi기반 토큰을 사용하는 재귀 하강 파서 구조- 심볼 테이블 없이 64K 세그먼트를 해시값으로 직접 접근
- 코드 생성은 OTCC 스타일로
ax레지스터를 결과 저장소로 사용
- 이후 바이트 스레드 코드(byte-threaded code) 실험을 통해 Forth식 구조를 시도했으나, 512바이트 내에서는 오히려 비효율적이어서 폐기됨
코드 최소화 기법
- 직선적 구조로 돌아가 468바이트 → 303바이트로 축소
-
점프 제거(fall-through) , tail-call, 호출 병합(call fusion) ,
stosw/lodsw활용, 8비트 점프 오프셋 유지 등의 기법 사용
-
점프 제거(fall-through) , tail-call, 호출 병합(call fusion) ,
- 207바이트 여유 공간을 확보해 추가 기능을 구현
완전한 C 기능 확장
- 200바이트 추가로 완전한 C 문법 지원 달성
- 중첩된
if/while블록, 다양한 *이항 연산자(+, -, , &, |, ^, <<, >>, ==, !=, <, >, <=, >=) - 함수 정의 및 재귀 호출, 인라인 어셈블리(asm) , 한 줄/여러 줄 주석 지원
- 연산자 테이블(
binary_oper_tbl)을 통해 각 연산자를 4바이트로 정의, 14개 연산자를 56바이트로 처리
- 중첩된
문법 구조
- 전체 문법은
program,var_decl,func_decl,statement,expr등으로 구성 -
//및/* */주석 모두 지원 - 문법 정의 텍스트 자체가 704바이트로, 실제 구현보다 큼
인라인 어셈블리와 런타임
-
asm구문을 통해 x86-16 기계어를 직접 삽입 가능- I/O 처리를 위해 필수적인 기능
- 런타임(
rt/)은 C로 작성된 두 파일로 구성-
rt/lib.c: 인라인 어셈블리 기반 라이브러리 루틴 -
rt/_start.c: 프로그램 진입점_start()
-
예제 프로그램
-
examples/hello.c: 텍스트를0xB8000메모리에 직접 출력 -
examples/sinwave.c: VGA 모드 0x13에서 사인파 애니메이션 표시 -
examples/twinkle.c: PC 스피커로 “Twinkle Twinkle Little Star” 연주 (소리 경고 포함)
결론
- SectorC는 불가능해 보이는 목표를 실현한 초소형 C 컴파일러로,
소프트웨어 최소화와 창의적 언어 설계의 극단적 사례를 보여줌 - 글의 마지막은 유머러스한 “무엇을 배웠는가” 선택지로 마무리되며,
불가능에 도전하는 태도와 소프트웨어 단순화의 가치를 풍자적으로 강조함
Hacker News 의견들
- 만약 이런 구현이 1980년대에 존재했다면, C 표준에는 서로 다른 토큰이 같은 16비트 값으로 해시 충돌을 일으킬 경우 정의되지 않은 동작(UB)을 유발한다는 규칙이 생겼을 것 같음
2000년대의 최적화 컴파일러들은 그런 토큰을 그냥 no-op으로 최적화해버렸을지도 모름 😉- “너는 -wTokenHashCollision 옵션을 켜지 않았잖아! 네 무지 때문에 UB가 발생한 거야. 명세는 완벽히 명확함!” 이라며 농담 섞인 반응을 남김
- 너무 현실적이라 웃음이 터졌음. LMAO
- 최근 내가 만든 X86-16 부트 섹터 C 컴파일러와 비슷해 보임
부트 섹터 게임을 만드는 건 정말 향수를 자극하는 마법 같은 경험임. 프로그래밍이 진짜 재미있던 시절이 떠오름
요즘 AI 시대에는 이런 프로젝트들이 너무 저평가되는 게 아쉬움
내 프로젝트 링크- 음, 글에서 다루는 건 완전한 C가 아닌, 512바이트 안에 들어가는 C 유사 언어 컴파일러임
네 프로젝트는 부트 섹터용 코드를 생성할 수 있는 옵션이 있는 것 같음.
둘 다 흥미롭지만, 공통점은 ‘boot sector’, ‘C’, ‘compiler’ 정도뿐임 - “프로그래밍이 진짜 재미있던 시절”이라는 말에 공감하지만, 이제는 더 많은 사람들이 같은 걸 할 수 있게 되었음
그래서 더 이상 내가 특별하지 않다는 생각이 듦
- 음, 글에서 다루는 건 완전한 C가 아닌, 512바이트 안에 들어가는 C 유사 언어 컴파일러임
- 이런 프로젝트는 현대 개발이 얼마나 하드웨어로부터 멀어졌는지를 상기시켜줌
우리는 추상화를 계속 쌓아올려서 “Hello World” 하나 돌리는데도 200MB의 node_modules가 필요함
그런데 누군가는 512바이트 안에 C 컴파일러를 넣음
모두가 부트 섹터 코드를 써야 한다는 건 아니지만, 이런 프로젝트를 읽는 건 정말 겸손해지는 경험임. 교육적인 가치도 큼 - 내가 바로 이 프로젝트의 작성자일지도 모름. 만드는 과정이 정말 즐거웠음!
- 정말 멋짐. 나도 미니멀한 C 컴파일러를 만들고 있는데, 부트 섹터에 맞추는 게 아니라 8비트 시스템을 목표로 함
이 프로젝트는 C의 핵심 구조가 얼마나 단순한지 잘 보여줌.
C가 B 언어에서, 그리고 B가 Fortran의 축소판에서 발전했다는 점이 흥미로움 - if, while, for를 단순한 goto 루틴으로 바꾸면 얼마나 더 작아질지 궁금함
어셈블리에는 결국 jmp밖에 없으니까 말임.
그리고 “choose your own adventure”가 아니라 “chose your own adventure”라고 해야 함 :)
나는 미니멀리즘을 사랑함 - 이 컴파일러나 ‘barely-C’ 개념은 부트스트래핑 체인에 활용할 수도 있을 것 같음
아주 작은 플랫폼 전용 바이너리에서 시작해 점점 더 복잡한 도구와 컴파일러를 만들어가는 식임
예시로 mishmashvm과 tcc_bootstrap_alt 프로젝트를 참고할 수 있음
- 정말 멋짐. 나도 미니멀한 C 컴파일러를 만들고 있는데, 부트 섹터에 맞추는 게 아니라 8비트 시스템을 목표로 함
- 아름다운 프로젝트임. 다만 제목에 2023을 빨리 추가하는 게 좋겠음
당시 논의는 이곳에서 있었음- 고마움! 매크로 확장 버전으로는
SectorC: A C Compiler in 512 bytes - 링크 - 2023년 5월 (댓글 80개) 정도로 정리할 수 있음
- 고마움! 매크로 확장 버전으로는
- 어제 HN에 올라온, Claude가 2주 만에 2만 달러로 만든 10만 줄짜리 C 컴파일러와 비교해보면 흥미로움
- 재밌는 비교지만, 그쪽은 리눅스 커널을 컴파일하고 여러 아키텍처용 코드를 생성할 수 있음
반면 이 프로젝트는 C의 일부만 처리 가능함
즉, 이건 완전한 C 컴파일러라기보다 C의 부분집합을 다루는 컴파일러임.
이 컴파일러로 컴파일 가능한 코드는 실제 C 컴파일러로도 가능하지만, 그 반대는 아님
- 재밌는 비교지만, 그쪽은 리눅스 커널을 컴파일하고 여러 아키텍처용 코드를 생성할 수 있음
- 토큰 해싱을 이용해 의사 심볼 테이블을 만드는 방식이 정말 우아함
- 나도 예전에 테스트 도구용 파서를 만들 때 심볼 해시값만으로 식별자를 관리한 적이 있음
다행히 32비트 충돌이 날 만큼 많은 심볼을 쓰진 않았길 바람 - 나도 같은 생각임. 해싱 토큰 트릭이 정말 멋짐
참고로 0x01e0~0x01fd 사이에 21바이트 여유 공간이 남아 있음. 뭔가 더 넣을 수 있을지도 ;)
- 나도 예전에 테스트 도구용 파서를 만들 때 심볼 해시값만으로 식별자를 관리한 적이 있음
- “atoi()가 일반 텍스트에 대해 나쁜 해시 함수처럼 동작한다”는 부분이 흥미로움
하지만 내가 기억하기로 atoi()는 유효하지 않은 입력에 대해 0을 반환하도록 정의되어 있었음 - 부트 섹터용 컴파일러에 대한 관심이 꽤 많음
Linux에서 실행할 때는 qemu 호출을 coreaudio 대신 alsa로 바꾸면 됨
이를 위한 pull request를 GitHub에 올렸음. 작성자가 내 verbose한 셸 스크립트 스타일을 괜찮게 본다면 머지될 수도 있음 - 정말 멋진 글이었음! 예전에 내가 만든 부트섹터 OS가 떠오름
이제 거기에 C 컴파일러를 추가해볼 때가 된 것 같음
내 OS 프로젝트 링크