
트랜스퓨터가 뭔지부터 짚고 갈게요
nanochess라는 닉네임으로 잘 알려진 오스카 톨레도(Oscar Toledo G.)가 1990년대에 직접 만들었던 트랜스퓨터용 C 컴파일러를 요즘 시스템에서도 빌드할 수 있게 손본 이야기를 공개했어요. 이 분은 512바이트짜리 부트섹터 안에 체스 게임을 욱여넣은 BootChess로 유명한데요, 사실 그보다 훨씬 전인 90년대에 아버지와 함께 트랜스퓨터 기반 컴퓨터를 직접 조립하고, 그 위에서 돌아가는 운영체제와 컴파일러까지 손수 짰던 이력이 있어요.
트랜스퓨터(Transputer)가 뭐냐면, 1980년대 영국 INMOS라는 회사가 만든 프로세서예요. 이름부터 트랜지스터와 컴퓨터를 합친 건데, 칩 하나가 곧 컴퓨터 하나라는 발상이었어요. CPU와 메모리, 그리고 다른 트랜스퓨터와 직접 연결되는 직렬 링크 4개가 한 칩에 들어 있어서, 레고 블록처럼 여러 개를 이어 붙이면 그대로 병렬 컴퓨터가 됐거든요. 지금으로 치면 멀티코어와 네트워크 인터페이스를 한 칩에 넣은 셈이에요. occam이라는 병렬 프로그래밍 전용 언어와 함께 나왔고, 프로세스 스케줄러가 하드웨어에 내장돼 있어서 컨텍스트 스위칭이 아주 빨랐어요. 지금은 사라진 아키텍처지만 명령어 구조가 워낙 독특해서 지금 봐도 배울 점이 많은 CPU예요.
왜 '이식 불가능한' 컴파일러였을까요
당시 컴파일러는 트랜스퓨터 위에서, 그것도 본인이 만든 운영체제 위에서만 돌아가도록 짜여 있었어요. 자기 자신을 컴파일해서 다음 버전을 만드는 셀프 호스팅(self-hosting) 방식이었는데요, 이게 뭐냐면 컴파일러 소스를 컴파일하려면 이미 그 컴파일러의 실행 파일이 있어야 하는 구조예요. 닭이 먼저냐 달걀이 먼저냐 같은 얘기인데, 원래 환경이 사라지면 소스가 있어도 빌드할 방법이 없어져 버려요.
여기에 혼자 쓰려고 만든 90년대 코드다 보니 온갖 암묵적인 가정이 깔려 있어요. 이런 코드가 현대 컴파일러에서 깨지는 이유는 대개 몇 가지로 모여요. 첫째, int와 포인터의 크기가 같다는 가정이에요. 트랜스퓨터는 32비트라 둘 다 4바이트였지만, 요즘 64비트 시스템에서는 포인터가 8바이트라 포인터를 int에 넣었다 빼면 값이 잘려 나가요. 둘째, char가 부호 있는 타입인지 없는 타입인지에 대한 가정이에요. C 표준은 이걸 구현체에 맡겨 두기 때문에 플랫폼마다 달라요. 셋째, 구조체 필드 정렬이나 바이트 순서 같은 메모리 레이아웃을 특정 하드웨어 기준으로 박아 놓은 코드예요. 넷째, 표준 라이브러리 대신 자기 운영체제의 시스템 호출을 직접 부르는 입출력 코드예요. 원래 환경에서는 아무 문제 없이 돌아가지만, 다른 곳으로 가져가는 순간 컴파일이 안 되거나, 더 나쁘게는 컴파일은 되는데 이상하게 동작하게 돼요.
이식 작업은 결국 '가정을 명시적으로 바꾸는 일'
이런 코드를 옮기는 작업은 사실 화려하지 않아요. 암묵적인 가정을 하나씩 찾아서 명시적인 코드로 바꾸는 반복 작업이거든요. 정수는 int 대신 int32_t 같은 고정 폭 타입으로 바꾸고, 파일 입출력은 표준 C 라이브러리 함수로 감싸고, 컴파일러 경고를 최대로 켜 놓고 하나씩 잡아 나가는 식이에요. 결과물은 요즘 리눅스나 맥에서 gcc나 clang으로 빌드되는 크로스 컴파일러예요. 크로스 컴파일러가 뭐냐면, 내 PC에서 돌아가지만 결과물은 다른 CPU용 실행 파일을 내놓는 컴파일러예요. 그 결과물을 본인이 공개해 둔 트랜스퓨터 에뮬레이터에서 돌려 볼 수 있으니, 30년 전 개발 환경이 오늘의 노트북 위에서 다시 살아나는 셈이에요.
트랜스퓨터용 코드 생성이 특히 흥미로운 이유도 있어요. 이 CPU는 범용 레지스터가 없고, A, B, C라는 3단짜리 평가 스택으로 연산을 해요. 지역 변수는 워크스페이스 포인터라는 레지스터가 가리키는 메모리 영역에 오프셋으로 접근하고요. 요즘 컴파일러들이 레지스터 할당에 온갖 공을 들이는 것과 달리, 트랜스퓨터 컴파일러는 스택 세 칸을 어떻게 굴릴지가 핵심이에요. 이런 아키텍처용 컴파일러 소스를 읽어 보는 건 컴파일러 백엔드가 실제로 뭘 하는지 감 잡기에 꽤 좋은 교재예요.
업계 맥락: 레트로 컴퓨팅과 소프트웨어 보존
이런 작업은 요즘 레트로 컴퓨팅 흐름과 맞닿아 있어요. 오래된 소프트웨어를 에뮬레이터로 살려 내는 시도는 MAME 같은 프로젝트부터 꾸준히 이어져 왔고, 최근에는 초기 유닉스나 옛 상용 컴파일러 소스를 현대 시스템에서 다시 빌드하는 프로젝트가 여럿 나왔어요. 핵심은 실행 파일만 남기는 게 아니라 '다시 빌드할 수 있는 상태'로 남겨야 진짜 보존이라는 거예요. 셀프 호스팅 컴파일러는 빌드 환경이 사라지면 화석이 되어 버리기 때문에, 이식 가능한 버전으로 바꾸는 작업이 곧 보존 작업이에요.
한국 개발자에게 주는 시사점
당장 트랜스퓨터를 쓸 일은 없겠지만, 여기서 얻어 갈 건 두 가지예요. 하나는 '이식 가능한 C를 쓴다'는 게 구체적으로 무슨 뜻인지 체감하는 거예요. 임베디드나 게임 엔진, 오래된 사내 라이브러리를 유지보수하다 보면 int 크기 가정이나 char 부호 문제로 한 번쯤 데이거든요. 고정 폭 타입 쓰기, 경고를 에러로 취급하기, 플랫폼 의존 코드를 한 파일로 격리하기 같은 습관이 왜 중요한지 살아 있는 사례로 보여 줘요. 다른 하나는 작은 컴파일러 소스를 직접 읽어 볼 기회라는 점이에요. LLVM은 너무 커서 엄두가 안 나지만, 한 사람이 쓴 C 컴파일러는 며칠이면 훑어볼 수 있는 크기거든요.
마무리
한 줄로 정리하면, 30년 전 특정 하드웨어에 묶여 있던 컴파일러를 '가정을 명시적으로 바꾸는' 지루한 작업을 통해 현대 시스템에서 되살린 이야기예요. 여러분이 유지보수하는 코드 중에 '이 환경 아니면 못 돌리는' 것이 있나요? 그걸 옮긴다면 가장 먼저 깨질 부분은 어디일 것 같으세요?
🔗 출처: Hacker News