Prolog를 Forth로 컴파일한다는 것: 극단적 미니멀리즘과 가상 머신의 만남
요즘 백엔드 엔지니어들과 아키텍처 리뷰를 하다 보면 가끔 숨이 막힐 때가 있습니다. 수백 메가바이트짜리 도커 이미지, 끝없이 깊어지는 마이크로서비스 호출 체인, 그리고 그 위에서 돌아가는 무거운 런타임들. 우리가 언제부터 이렇게 컴퓨팅 자원을 낭비하게 되었나 싶죠.
이런 와중에 Hacker News에 흥미로운 아티클이 하나 올라왔습니다. Journal of Forth Application and Research(JFAR)에 실렸던 고전적인 페이퍼, 바로 Compiling Prolog to Forth 입니다. 원본 PDF는 오래된 스캔본의 형태를 띠고 있지만, 이 페이퍼가 다루는 아키텍처적 발상은 15년 차 엔지니어인 제 관점에서도 여전히 날카롭고 아름답습니다.
오늘은 이 논문이 왜 단순한 과거의 유물이 아닌지, 그리고 고수준의 논리 프로그래밍 언어를 가장 저수준의 스택 머신으로 컴파일하는 과정에서 우리가 무엇을 배울 수 있는지 딥다이브 해보겠습니다.
왜 하필 Prolog와 Forth인가?
이 두 언어는 프로그래밍 언어 스펙트럼의 양극단에 있습니다.
- Prolog: 선언적(Declarative) 언어의 끝판왕입니다. 개발자는 ‘어떻게’를 정의하지 않고 ‘무엇’을 정의합니다. Unification(단일화)과 Backtracking(역추적)이라는 강력하지만 무거운 실행 모델을 엔진이 알아서 처리합니다.
- Forth: 명령형(Imperative) 언어이자 스택 기반의 극단적 미니멀리즘 언어입니다. 문법이랄 게 거의 없고, 그저 스택에 데이터를 밀어 넣고 빼내며 함수(Word)를 실행합니다. OS 없이 베어메탈 위에서도 돌아가는 가벼움이 특징입니다.
가장 추상화 레벨이 높은 로직을, 가장 기계어에 가까운 스택 조작으로 변환한다? 처음 들으면 미친 짓 같지만, 사실 컴파일러 설계 관점에서는 이보다 더 완벽한 궁합도 없습니다.
기술적 딥다이브: WAM과 스택 머신의 교차점
Prolog를 효율적으로 실행하기 위해 1983년 David H. D. Warren은 Warren Abstract Machine(WAM) 이라는 가상 머신 아키텍처를 고안했습니다. WAM은 Prolog의 실행 상태를 관리하기 위해 여러 개의 메모리 영역(Heap, Stack, Trail, PDL)과 특화된 명령어 셋을 정의합니다.
여기서 Forth의 진가가 발휘됩니다. Forth는 단순히 언어가 아니라, 그 자체로 가상 머신이자 매크로 어셈블러입니다. C나 C++로 WAM을 처음부터 구현하려면 복잡한 파서와 인터프리터 루프를 작성해야 하지만, Forth를 사용하면 WAM의 명령어들을 Forth의 Word로 1:1 매핑해버리면 그만입니다.
\ 개념적인 WAM 명령어의 Forth 구현 예시
: put_variable ( Xi Xj -- )
\ Heap에 새 변수를 할당하고 레지스터에 바인딩하는 로직
allocate_heap_cell
dup bind_to_register_i
bind_to_register_j
;
: call ( procedure -- )
\ 환경을 저장하고 프로시저로 점프
save_environment
execute
;
위와 같이 Forth로 WAM 명령어 셋을 구현하면, Prolog 소스 코드를 파싱하여 이 Forth Word들의 나열로 변환할 수 있습니다. Forth는 내부적으로 Threaded Code 방식을 사용하기 때문에, 이렇게 생성된 코드는 일반적인 인터프리터의 거대한 switch-case 루프(Dispatch overhead) 없이 함수 포인터를 따라 극도로 빠르게 실행됩니다.
나의 생각: 우리는 왜 이 구조를 다시 봐야 하는가
솔직히 말해서, 2024년에 실무에서 Prolog를 Forth로 컴파일해서 프로덕션에 배포할 미친 CTO는 없을 겁니다. (만약 있다면 저와 커피 한잔하시죠. 이야기 좀 들어보고 싶네요.)
하지만 제가 이 페이퍼를 보고 전율을 느낀 이유는, 이 구조가 현재 우리가 열광하는 최신 기술들의 근본적인 철학과 맞닿아 있기 때문입니다.
WebAssembly(Wasm)를 생각해 보세요. 고수준 언어(Rust, Go, 심지어 Python)를 브라우저나 엣지 환경에서 빠르게 실행하기 위해, 우리는 결국 단순하고 안전한 스택 기반 가상 머신 으로 돌아왔습니다. Prolog를 Forth로 컴파일하는 과정은, 거대한 런타임 오버헤드를 피하고 도메인 특화 언어(DSL)를 가장 효율적인 타겟 아키텍처로 내리는 컴파일러 디자인의 정수입니다.
과거에 제가 대규모 룰 엔진을 설계할 때, 복잡한 비즈니스 로직을 평가하는 트리를 만들고 이를 순회하는 인터프리터를 C++로 짠 적이 있습니다. 그때 성능 병목을 해결하기 위해 결국 바이트코드를 정의하고 스택 머신을 만들었는데, 이 페이퍼에서 설명하는 Forth를 이용한 메타 컴파일러 접근 방식을 그때 알았더라면 구조가 훨씬 우아해졌을 겁니다.
Hacker News의 반응
이번 HN 스레드에서도 이런 레트로 컴퓨팅의 우아함에 매료된 엔지니어들의 반응을 엿볼 수 있습니다. 현대의 LLVM이나 V8 같은 거대한 컴파일러 인프라도 훌륭하지만, 단 한 명의 엔지니어가 머릿속에 온전히 담을 수 있는 크기의 시스템(Forth)으로 강력한 추상화(Prolog)를 구현해낸다는 것은 엔지니어링의 로망 그 자체입니다.
결론
이 페이퍼는 당장 내일 출근해서 Jira 티켓을 쳐내는 데는 아무런 도움이 되지 않을 겁니다. 하지만 당신이 단순히 프레임워크의 API를 호출하는 코더를 넘어, 시스템의 바닥부터 꼭대기까지 꿰뚫어 보는 진짜 ‘엔지니어’가 되고 싶다면 이런 아키텍처를 고민해 봐야 합니다.
추상화의 비용은 결코 공짜가 아닙니다. 때로는 가장 밑바닥의 스택 머신을 직접 들여다볼 때, 가장 우아한 아키텍처의 영감을 얻을 수 있습니다.
References
- Original Article: Compiling Prolog to Forth [pdf]
- Hacker News Thread: https://news.ycombinator.com/item?id=47240169