
내가 만든 언어가 스스로를 컴파일하기 시작한 날
요약
AI 에이전트 작성을 위해 설계된 프로그래밍 언어 'machin'이 셀프 호스팅(self-hosting)에 성공하며 컴파일러로서의 완성도를 증명했습니다. 이 언어는 AI 친화적인 설계를 바탕으로 작성 시에는 스크립트처럼 쉽고, 실행 시에는 C 수준의 성능을 제공합니다.
핵심 포인트
- AI 에이전트가 코드를 작성하기 최적화된 언어 설계
- 컴파일러가 자기 자신을 컴파일하는 부트스트랩 과정 달성
- 스크립트의 편의성과 C 언어급의 실행 성능 결합
- 토큰 단위 설계 및 타입 어노테이션 제거를 통한 AI 친화성
1년 전 저는 프로그래밍 언어를 하나 만들었습니다. 그리고 올해, 그것은 장난감과 진짜 컴파일러(compiler)를 구분하는 경계선을 넘었습니다. 바로 스스로를 컴파일하기 시작한 것입니다.
(저 GIF는 실제 상황입니다 — machin이 자신의 컴파일러를 컴파일하고, 그 바이너리가 자신의 소스 코드를 바이트 단위로 그대로 재현하는 모습입니다.)
이 문장은 마치 마술 쇼의 묘기처럼 들릴지도 모릅니다. 하지만 그렇지 않습니다. 이것은 시스템 엔지니어링 (systems engineering)에서 가장 오래되고 어려운 증명 중 하나입니다. 그리고 AI를 페어 프로그래밍 (pair programming) 파트너로 삼아 이 단계에 도달한 것은, 제가 실제로 소프트웨어를 어떻게 구축하는지를 보여주는 가장 명확한 증거입니다.
먼저, 배경 이야기부터
machin은 특정 사용자를 위해 설계된 언어입니다. 코드를 읽는 인간이 아니라, **코드를 작성하는 AI 에이전트 (AI agent)**를 위한 언어입니다. 타입 어노테이션 (type annotations)은 전혀 없고, 한 줄당 하나의 정전적 선언 (canonical declaration)을 가지며, 모든 설계 선택은 토큰 (tokens) 단위로 측정됩니다. 그런 다음 C를 통해 단일 네이티브 바이너리 (native binary)로 직접 컴파일됩니다. 작성할 때는 스크립트처럼 쉽고, 실행할 때는 C급 성능을 냅니다. (왜 이전 포스트에서 설명했듯이 AI 에이전트를 위한 언어를 만들었는지에 대해 썼습니다.)
1년 동안 이 언어는 실제 도구들이 성장하는 방식대로, 즉 사용됨으로써 성장했습니다. 웹 서버, 데이터베이스 드라이버, WebSocket 클라이언트, 암호화, 심지어 게임까지 사용되었습니다. 하지만 언어의 신뢰성은 컴파일러의 신뢰성과 직결되며, machin의 컴파일러는 Go로 작성되었습니다. 그래서 저는 모든 언어가 결국 답해야 하는 질문을 던졌습니다.
machin은 스스로를 컴파일할 수 있을까?
"스스로 컴파일한다"는 것이 실제로 의미하는 것
모든 진지한 컴파일러는 **부트스트랩 (bootstrap)**이라 불리는 통과의례를 겪습니다. 컴파일러를 그 언어 자체로 다시 작성한 다음, 기존의 컴파일러를 사용하여 새로운 컴파일러를 컴파일하는 과정입니다. 만약 그 언어가 진짜라면 — 즉, 충분히 표현력이 있고, 정확하며, 충분히 빠르다면 — 새로운 컴파일러가 작동합니다. 그렇지 않다면, 그 사실을 곧바로 알게 될 것입니다.
그 황금 표준은 **고정점 (fixpoint)**입니다:
- 원래의 컴파일러가 셀프 호스팅 (self-hosted) 컴파일러를 빌드합니다.
- 그 셀프 호스팅 컴파일러가 _자신의 소스 코드_를 컴파일하여 새로운 네이티브 바이너리 (native binary)를 생성합니다.
- 그 새로운 바이너리가 동일한 소스 코드를 다시 컴파일합니다. 이때 결과물은 **바이트 단위로 완전히 동일 (byte-for-byte identical)**해야 합니다.
이 바이트들이 일치할 때, 당신은 그것이 단순한 의견의 문제가 아니라 증거임을 얻게 됩니다. 컴파일러가 자신을 정확하게 재현해낸 것입니다. 이제 컴파일러는 스스로 독립하여 존재합니다.
Machin은 현재 이 작업을 수행합니다. 렉서 (lexer), 파서 (parser), 타입 체커 (type checker), 그리고 C 코드 생성기 (C code generator)를 포함한 약 4,000줄에 달하는 전체 파이프라인 (pipeline)이 _machin 언어_로 작성되었으며, 원래의 컴파일러와 바이트 단위까지 동일한 머신 코드 (machine code)를 생성합니다.
실제로 어떻게 구축되었는가 (이 부분이 핵심입니다)
저는 "내 컴퓨터에서는 잘 돌아간다"는 식의 결과물을 내놓지 않습니다. 저는 _증거_를 내놓습니다. 따라서 모든 단계는 **바이트 차이 오라클 (byte-diff oracle)**을 기준으로 구축되었습니다. 즉, 원래의 컴파일러와 새로운 컴파일러를 동일한 입력값에 대해 실행한 뒤, 제가 이미 작성해 둔 실제 프로그램들의 전체 생태계에 걸쳐 출력값을 한 글자씩 비교했습니다. 특정 테스트 케이스가 아니라, 코퍼스 (corpus) 내의 모든 프로그램에서 그 차이(diff)가 나타나지 않을 때까지 해당 단계는 "완료"된 것이 아니었습니다.
그러한 엄격함은 즉각적인 보상으로 돌아왔습니다. 컴파일러를 자체 언어로 구축하는 과정은 1년 동안 앱을 만드는 것보다 해당 언어를 더 혹독하게 스트레스 테스트 (stress-tested)했으며, 원래 컴파일러에 숨어 있던 **세 가지 실제 버그 (genuine bugs)**를 찾아냈습니다. 내용 대신 메모리 주소를 비교하던 문자열 비교 로직, 문자열 처리에서의 이차 시간 복잡도 (quadratic) 저하, 그리고 코드 생성 시의 이스케이프 (escaping) 예외 케이스가 그것이었습니다. 이 버그들은 모두 셀프 호스팅 컴파일러와 참조 컴파일러가 단 1바이트라도 일치하지 않았기 때문에 발견되었으며, 저는 그 차이를 그냥 지나치기를 거부했습니다.
그다음은 정직한 수치입니다. 셀프 호스팅 컴파일러 (self-hosted compiler)는 단순히 작동만 하는 것이 아닙니다. 자신의 소스 코드에 대해 전체 파싱(parse)-타입 체크(typecheck)-코드 생성(codegen) 과정을 수행할 때, 원본의 약 0.9배 속도로 실행됩니다. 자신을 빌드했던 컴파일러보다 약간 더 빠릅니다. (처음부터 이랬던 것은 아닙니다. 첫 번째 버전은 7배나 더 느렸으며, 그 격차를 줄이는 과정은 겉보기에 명백해 보이는 병목 지점이 아닌 진짜 병목 지점을 찾아내는 것을 의미했습니다. 그것은 모두가 예상하는 '느린 조회(slow lookups)'가 아니라, 문자열 생성(string building) 문제였습니다.)
제가 이 이야기를 하는 이유
제가 AI를 활용해 구축한다는 말이 바로 이런 의미이기 때문입니다.
"챗봇에 프롬프트를 입력하고 나온 결과물을 그대로 출시했다"는 뜻이 아닙니다. 정반대입니다. AI는 엔진이었지만, 엔지니어링 (engineering) — 즉, 오라클(oracles), 바이트 수준의 검증(byte-level verification), 통과하는 테스트보다 통과하는 증명(proof)을 수용하려는 태도, 무엇이 빠르고 무엇이 빠르지 않은지에 대한 정직함 — 이 부분이 결과물을 신뢰할 수 있게 만드는 요소입니다. 이러한 엄격함(rigor)이 없는 AI는 그럴듯한 코드를 만들어낼 뿐입니다. 하지만 이러한 엄격함이 동반된 AI는 바이트 단위로 스스로를 컴파일하는 컴파일러를 만들어내며, 여러분은 직접 그 바이트를 확인해 볼 수 있습니다.
가드레일로서의 시니어 엔지니어링 판단력과 승수(multiplier)로서의 AI라는 이 조합은, 제가 Intrane에서 고객들을 위해 구축하는 시스템에 가져다주는 핵심 가치입니다. 대부분의 팀은 둘 중 하나만을 가집니다. 속도 없는 엄격함, 혹은 엄격함 없는 속도 말입니다. 흥미로운 작업은 이 둘이 만나는 지점에서 일어납니다.
스스로를 컴파일하는 언어는 그들이 이 두 가지를 모두 충족할 수 있다는, 어렵고 검증 가능하며 약간은 집요한 증거입니다. 여러분의 가장 어려운 문제에 이런 종류의 엔지니어가 필요하다면, 함께 이야기해 봅시다.
machin은 오픈 소스(MIT)입니다. 셀프 호스팅 컴파일러는 selfhost/에 있으며, 여러분이 직접 고정점(fixpoint)을 재현해 볼 수 있습니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기