
네이티브로 컴파일되는 Clojure, Jank
Jank라는 언어, 들어보셨나요? 한 마디로 표현하면 "네이티브로 컴파일되는 Clojure"예요. Clojure는 원래 JVM 위에서 도는 함수형 언어인데, 시작이 느리고 메모리도 많이 먹는다는 단점이 있거든요. 그래서 Jeaye Wilkerson이 만든 Jank는 같은 Clojure 문법을 쓰면서도 C++과 LLVM을 통해 네이티브 바이너리로 컴파일되는 걸 목표로 하고 있어요.
그런데 최근 Jank가 큰 변화를 발표했어요. 자체 중간 표현, 즉 IR(Intermediate Representation)을 새로 도입한 거예요. 지금까지는 Clojure 소스를 직접 C++ 코드로 변환했는데, 이제는 그 중간에 Jank만의 IR을 두기로 한 거죠.
IR이 뭐고, 왜 새로 만들었나
IR이 뭐냐면, 컴파일러가 소스 코드와 최종 기계어 사이에 두는 중간 단계의 코드 표현이에요. LLVM IR이 가장 유명하죠. 보통 컴파일러는 "고수준 언어 → IR → 기계어" 식으로 단계를 나눠서 작업해요. 그래야 각 단계에서 최적화를 할 수 있거든요.
문제는 Clojure 같은 동적 언어를 LLVM IR로 바로 떨구면, LLVM이 그 안에 숨어있는 정보를 제대로 못 본다는 거예요. 예를 들어 Clojure의 키워드 :foo는 항상 같은 객체를 가리키니까 비교가 포인터 비교 한 번이면 끝나는데, LLVM은 이걸 일반 함수 호출처럼만 보고 최적화 기회를 놓치는 거죠.
Jank의 새 IR은 이런 문제를 해결하기 위해 나왔어요. Clojure의 시맨틱을 잘 아는 IR이라서 다음과 같은 최적화가 가능해져요.
불필요한 박싱(boxing) 제거 가 첫 번째예요. Clojure는 모든 값이 객체라서 정수도 박싱해서 다루는 게 기본이에요. 그런데 코드를 잘 보면 박싱이 필요 없는 구간이 많거든요. 새 IR에서는 이런 구간을 추적해서 박싱을 생략할 수 있어요.
메타데이터 흐름 추적 도 중요해요. Clojure는 함수에 타입 힌트(^long, ^String 같은 거)를 붙일 수 있는데요. 이게 어디까지 전파되는지 IR 레벨에서 따라가면서 더 빠른 디스패치 코드를 만들 수 있어요.
똑똑한 인라이닝 결정 도 가능해져요. 어떤 함수를 호출할 때 그 함수 본문을 그대로 끼워 넣는 게 인라이닝인데, 이게 항상 좋은 건 아니에요. 코드가 커지면 캐시 미스가 늘거든요. Jank IR은 Clojure 함수의 특성을 고려해서 더 똑똑한 인라이닝 판단을 해요.
블로그 글의 벤치마크가 인상적이에요. 단순한 산술 루프에서 기존 방식 대비 수십 퍼센트 빨라졌고, 일부 벡터 연산은 두 배 이상 빨라졌다고 해요.
자체 IR이 트렌드가 된 이유
언어를 만드는 사람들 사이에서 "자체 IR을 만들지, LLVM에 의존할지"는 오래된 논쟁이에요. Rust도 처음엔 LLVM에만 의존했다가 MIR(Mid-level IR)을 도입했어요. Swift도 SIL(Swift Intermediate Language)이라는 자체 IR이 있고요. Julia는 Julia IR이라는 자체 표현을 거쳐 LLVM으로 넘어가요.
공통된 깨달음은 이거예요. "내 언어의 의미를 LLVM이 다 이해할 거라고 기대하지 마라." LLVM은 강력하지만 결국 C/C++을 위해 설계된 도구라서, 동적 언어나 함수형 언어의 특성을 표현하기에는 부족한 부분이 있거든요. 비슷하게 GraalVM의 Truffle 프레임워크나 Mono의 Roslyn 같은 것도 자체 IR 개념을 활용해요. JVM의 바이트코드도 일종의 IR이고요. 즉, "고수준 시맨틱을 보존하는 중간 단계가 필요하다"는 건 업계의 합의가 되어가는 흐름이에요.
한국 개발자에게는
Jank를 당장 프로덕션에 쓸 분은 많지 않을 거예요. 아직 1.0도 아니거든요. 그런데 이 발표가 시사하는 바는 커요.
첫째, 컴파일러를 공부할 때 LLVM 책만 보면 안 된다는 거예요. 요즘 진지한 언어 프로젝트는 다 자체 IR을 갖고 있어요. 둘째, JVM에서 네이티브로 가는 흐름이 점점 강해진다는 거예요. GraalVM Native Image, Kotlin/Native, Scala Native, 그리고 이제 Jank까지요. 한국 백엔드 개발자분들이 많이 쓰는 Kotlin이나 Spring 진영에서도 이 흐름을 무시할 수 없게 되었어요. 셋째, 만약 여러분이 DSL을 만들거나 자체 언어를 설계할 일이 있다면, IR 설계가 핵심 의사결정이 된다는 점을 기억해두세요.
마무리
Jank의 자체 IR 도입은 "LLVM은 만능이 아니다"라는 교훈을 다시 한번 보여줘요. 언어의 특성을 살리려면 그에 맞는 도구를 직접 만들어야 할 때가 있다는 거죠.
여러분이 만약 새 언어나 DSL을 만든다면, LLVM 같은 기존 백엔드에 바로 붙일까요, 아니면 자체 IR을 먼저 설계할까요?
🔗 출처: Hacker News
TTJ 코딩클래스 정규반
월급 외 수입,
코딩으로 만들 수 있습니다
17가지 수익 모델을 직접 실습하고, 1,300만원 상당의 자동화 도구와 소스코드를 받아가세요.
"비전공 직장인인데 반년 만에 수익 파이프라인을 여러 개 만들었습니다"
실제 수강생 후기- 비전공자도 6개월이면 첫 수익
- 20년 경력 개발자 직강
- 자동화 프로그램 + 소스코드 제공