TECH 으로 돌아가기
TECH HACKER NEWS 오늘 7분 읽기 29 READS

Rust를 읽기 좋은 C로 바꾸는 Eurydice, 어디까지 쓸 수 있나

몇 년 전만 해도 Rust 코드를 컴파일하는 방법은 LLVM을 백엔드로 삼는 rustc가 사실상 유일했다. 그 사이 mrustc, GCC의 Rust 지원(gccrs), rust_codegen_gcc, Cranelift 같은 프로젝트들이 Rust 컴파일러 구현을 다변화해 왔다. 가장 최근에 등장한 Eurydice는 여기서 한 걸음 더 나아간다. Rust 코드를 LLVM IR이나 기계어가 아니라 사람이 읽을 수 있는 깨끗한 C 코드로 변환하는 것을 목표로 삼는다.

Eurydice는 2023년에 시작됐고 MIT와 Apache-2.0 라이선스가 섞여 있다. 프랑스 국립 컴퓨터과학 연구기관 Inria와 마이크로소프트 소속 인력들이 유지보수하는 Aeneas 프로젝트의 일부로, 외부 기여도 받는다. Aeneas는 Rust 코드에 형식 검증(formal verification) 도구를 적용하기 위한 여러 도구를 개발하는 상위 프로젝트다.

왜 굳이 C로 바꾸나

얼핏 보면 이상한 목표다. Rust는 더 안전하고 현대적인 언어인데, 왜 이를 낡은 C로 되돌리려 할까. 답은 고신뢰(high-assurance) 소프트웨어 생태계에 있다. 기존의 검증·규제 준수 도구 상당수가 C를 입력으로 전제하고 만들어져 있기 때문이다. 이런 도구들이 Rust를 직접 다룰 수 있게 되기 전까지, Eurydice는 Rust로 작성한 코드를 기존 C 기반 검증 파이프라인에 태울 수 있는 다리 역할을 한다. 또 C 컴파일러는 있지만 쓸 만한 Rust 컴파일러가 없는 환경에서도 징검다리가 된다. 실제로 일부 포스트-퀀텀 암호(post-quantum cryptography) 루틴을 Rust에서 C로 변환하는 데 쓰인 사례가 있다.

Eurydice의 내부 구조는 일반적인 컴파일러와 같다. Rust 프로그램을 중간 표현(IR)으로 바꾸고, 일련의 패스로 IR을 변형한 뒤, 더 낮은 수준의 언어인 C로 출력한다. 다만 결정적 차이가 있다. 보통의 컴파일러는 기계어 효율을 위해 코드 구조를 뭉개지만, Eurydice는 원래 코드의 전체 구조를 최대한 보존하면서 Rust에만 있고 C에는 없는 요소만 걷어낸다. 가장 많이 기여한 Jonathan Protzenko가 블로그에서 이 접근을 설명했다. 예컨대 최대공약수로 최소공배수를 구하는 함수를 rustc로 돌리면 비트 연산으로 가득 찬 뒤엉킨 루프가 나오지만, Eurydice는 원본 구조를 유지한 C를 내놓는다.

구조 보존이라는 까다로운 과제

구조를 지키려면 의미까지 지켜야 한다. Rust는 곱셈이 오버플로로 패닉을 일으킬 경우 그것이 다른 함수 호출의 부수효과보다 먼저 일어남을 보장하지만, C는 곱셈이 별도 문장으로 분리돼 있을 때만 그런 평가 순서를 보장한다. 그래서 Eurydice는 필요한 자리에 임시 변수(예시의 uu____0 같은)를 끼워 넣어 원래의 평가 순서를 복원한다. 이 C 코드가 과연 '읽기 좋은지'는 취향의 문제겠지만, 적어도 원본의 흐름은 살아 있다.

물론 모든 Rust 프로그램을 C로 충실히 옮길 수 있는 것은 아니다. 범위가 아닌 이터레이터를 쓰는 for 루프는 Eurydice의 지원 코드를 호출해 상태를 관리하는 while 루프로 바뀐다. 더 근본적으로 C에는 제네릭 개념이 없어, Rust 코드를 변환 과정에서 단형화(monomorphize)해야 한다. 그 결과 타입만 다른 거의 동일한 함수가 여러 벌 생기는데, 더 관용적인 C라면 매크로나 void * 인자로 처리했을 부분이다. 동적 크기 타입도 까다롭다. 어떤 코드 경로는 타입 크기를 알고 어떤 경로는 모르는 구분이 검증 결과에 직접 영향을 주기 때문에, Eurydice는 유연 배열 멤버를 쓰는 버전과 고정 길이 배열을 쓰는 버전 두 가지 타입을 함께 내보낸다. 둘 사이 변환은 런타임상 아무 비용이 없지만 C의 엄격한 앨리어싱 규칙을 어기므로, Protzenko는 생성 코드를 -fno-strict-aliasing으로 컴파일하라고 권한다.

이처럼 더 추상적인 언어를 구조를 보존한 채 C로 내리는 방식 자체는 새롭지 않다. Eurydice가 기반으로 삼은 KaRaMeL 프로젝트는 암호 라이브러리 개발에 쓰이는 의존 타입 함수형 언어 F를 같은 방식으로 C로 변환해 왔다. 증명된 F 프로그램을 동등한 C로 바꿔 성능이 중요한 환경에서 쓰게 하는 것이다. Eurydice는 Rust용 파서·타입체커를 직접 만드는 대신 또 다른 Aeneas 도구 Charon을 써서 rustc로부터 전처리된 MIR을 JSON으로 뽑고, 이를 KaRaMeL의 IR로 변환한 뒤 F* 때와 같은 코드 생성 로직에 넘긴다.

현실적 한계와 쓸모

문제는 확장성이다. 기사 필자가 여러 Rust 패키지로 Charon을 시험했을 때, const 제네릭 같은 비교적 최신 기능 앞에서 번번이 막혔다. 결국 Eurydice는 복잡한 기능을 피한 작고 자족적인 프로그램에서 가장 잘 돌아간다. 그 영역 안에서는 실제로 잘 작동해, 추가 임시 변수나 글루 코드가 필요한 부분을 빼면 원본 Rust의 구조가 그대로 유지된다. 다만 역설이 있다. 작고 자족적인 코드일수록 손으로 다시 C로 옮기기도 쉽다는 점이다. 따라서 Eurydice를 도입할 실익은, 원본 Rust를 계속 수정하면서 그에 맞춰 C 버전을 자동으로 동기화하고 싶을 때에 주로 생긴다. 지금으로서는 Rust를 더 많은 환경에 끼워 넣기 위한, 빠르게 늘어나는 도구 모음의 가장 새로운 한 조각으로 보는 편이 정확하다.

SOURCE · HACKER NEWS
원문 전체 보기 → https://lwn.net/Articles/1055211/
SHARE
NEXT · CHOOSE

변화를 읽었다면,
내가 만들 수익 구조를 고릅니다.

정보를 더 모으는 데서 멈추지 않고, 광고·외주·판매·중개·구독 중 내 상황에 맞는 출발점을 정해보세요.

21가지 수익 구조 살펴보기 →
처리 중...