황금 못, 그리고 Vale(n) 프로그래밍 언어의 부활
요약
Valen은 Rust 라이브러리의 제네릭과 메모리 안전성을 활용하여 언어 경계를 넘는 실험적인 시스템 프로그래밍 언어입니다. 기존의 C ABI 방식 대신, rustc 내부 API와 컴파일러 패치를 이용해 Valen 타입이 Rust 트레이트를 구현하고 양방향 호출을 가능하게 하는 것이 핵심 목표입니다. 이를 통해 개발자는 Rust 생태계의 강력한 기능을 유지하면서도 특정 사용 사례에 더 단순하고 편리한 언어를 구축할 수 있습니다.
핵심 포인트
- Valen은 Rust 라이브러리 연동을 위해 컴파일러 패치 및 내부 API를 활용합니다.
- C ABI의 한계를 넘어, 제네릭과 메모리 안전성을 확보하는 것이 목표입니다.
- Rust 트레이트 구현 Valen 클로저 생성 등 고급 기능을 추구합니다.
- 궁극적으로 Rust 생태계의 장점을 유지하며 사용하기 편리한 언어를 만드는 것이 목적입니다.
Valen 은 Rust 라이브러리를 직접 활용하면서 언어 경계를 넘는 제네릭과 메모리 안전성을 지원하려는 실험적 언어이며, Rust 그래픽 라이브러리를 호출해 첫 렌더링에 성공함
C ABI를 통한 연동 대신 rustc 내부 API 와 컴파일러 패치를 이용해, Valen의 타입이 Rust 트레이트를 구현하고 Rust 제네릭 함수가 Valen 코드를 다시 호출하도록 연결함
두 컴파일러의 단형화 처리와 LLVM 코드 생성 을 연계하되, Rust MIR로 표현하기 어려운 기능을 위해 Valen의 자체 중간 표현과 백엔드를 유지함
그룹 빌림(group borrowing) 으로 공유 가능한 가변 참조와 언어 경계의 빌림 검사를 구현 중이며, 일부 경계 검사는 작동하지만 완성되지 않았고 성능 향상 등의 추가 이점도 아직 검증되지 않음
기존 Vale와 달리 시스템 프로그래밍 언어 를 지향하며 Rust 같은 unsafe
를 둘 예정이지만, 현재 언어 간 구조체 연동은 크기가 0인 타입으로 제한되고 클로저의 그룹 빌림 등도 미완성임
두 컴파일러를 잇는 황금 못
황금 못(golden spike) 은 1869년 5월 10일, 6년간의 공사 끝에 미국 동서 철도망을 연결하며 Utah의 Promontory Summit에 박은 17.6캐럿 기념 못임
여기서는 서로 멀리 떨어진 시스템을 연결하는 어려운 작업을 뜻함
Google Earth의 로컬 편집 앱과 클라우드 저장소를 연결해 온라인 편집을 가능하게 한 프로젝트에도 같은 이름을 사용한 경험이 있음
일반적인 언어 간 연동은 C ABI와 래퍼 함수 에 의존하지만, C에는 제네릭이 없고 이 경로만으로는 언어 경계를 넘는 메모리 안전성을 확보할 수 없음
Valen 은 기존 Vale 와 닮았지만 다른 언어로, Rust 생태계와 긴밀하게 연동 하는 것을 목표로 함
직접적인 활용 대상은 wgpu 기반 화면 공간 굴절 그래픽 라이브러리 Glass Domino 임
컴파일러 이름은 valenc
이며, “Valence”처럼 발음할 수 있음
매우 실험적인 상태로, 공개적으로 널리 사용하기 전 정리와 재작성, 안정화 작업이 남아 있음
Rust 생태계를 유지하면서 바꾸고 싶은 것
목표는 Rust의 속도, 안전성, 라이브러리 생태계 를 활용하면서 특정 사용 사례에서 더 단순하고 편리한 언어를 만드는 것임
핵심 기능으로 다음을 추구함
추가 희망 기능은 더 나은 비동기 처리와 열거형 , mustprogress
최적화, 트레이트를 구현하는 클로저, 통합 함수 호출 문법, 빠른 컴파일임
구체적 목표: Rust 이벤트 루프에 Valen 콜백 전달
목표 프로그램은 Rust의 NobiliaWindow
, FrameInput
, MainLoopCallback
을 가져와 1200×900 창 을 만들고, 방향키 입력으로 카메라를 회전시킴
MainLoopCallback((win, input) => { ... })
형태로 Rust 트레이트를 구현하는 Valen 클로저 를 즉석에서 생성해 main_loop
에 전달함
이를 위해 가져올 수 있는 Rust 항목 , 정적 메서드와 매개변수, 트레이트 메서드를 파악하고, Rust에서 Valen으로 되돌아오는 호출을 처리해야 함
양방향 함수 인라이닝 가능 여부와 단형화 통합도 해결해야 할 질문에 포함됨
Rust의 제네릭과 트레이트 체계를 새로 구현하는 대신, 필요한 Rust 함수에 대응하는 Valen AST 함수 시그니처를 지연 생성 하는 방법을 택함
기존 Vale에서 발전시킨 Valen의 제네릭 및 트레이트 처리 체계를 재사용할 수 있음
C ABI와 언어 간 제네릭의 한계
Rust 라이브러리를 다른 언어에서 호출할 때는 흔히 extern "C"
와 repr(C)
를 사용함
Rust에 안정된 ABI가 없기 때문 이기도 함
ABI는 구조체 값 전달을 레지스터 또는 참조로 처리할지, 필드를 재배치해 공간을 줄일지 같은 규칙을 포함함
목표 함수인 main_loop<C: MainLoopCallback>(&mut self, cb: &mut C)
는 제네릭 함수 이므로, 제네릭이 없는 C ABI만으로는 필요한 연동을 해결할 수 없음
언어 간 제네릭에는 두 단계가 있음
첫 단계는 다른 언어에서 Rust 제네릭 함수를 호출 하는 것임
두 번째는 호출된 Rust 제네릭 함수에서 Valen 코드를 다시 호출 하는 것임
첫 단계의 예는 Valen에서 Vec<int>
를 만들고 push(21)
, push(42)
, pop().unwrap()
을 호출해 42를 얻는 것임
2024년 실험: 문서 생성기와 C 래퍼를 이용한 접근
이전 Rust 연동 실험 은 C에서 Vec<u64>
를 가져오고 메서드를 지정해 Rust 제네릭 타입을 사용하는 경로 를 만들었음
with_capacity(42)
로 생성한 벡터의 용량을 출력하면 Capacity: 42
를 얻었음
구현은 여러 외부 실행 단계를 거침
rustdoc
의 JSON 출력 을 rustdoc_types
로 읽어 사용 가능한 타입과 impl
의 메서드를 파악함
rustc
로 크기를 출력하는 탐색 프로그램을 실행함. 예를 들어 size_of::<Vec<u64>>()
의 결과는 24바이트였음
다시 rustc
를 실행해 인스턴스화 프로그램으로 C 래퍼 라이브러리 를 만들고 C 프로그램에 정적으로 연결함
제네릭 인수를 완전히 명시하고, 사용할 메서드마다 #pragma rsfn
을 작성 해야 했음
Vale와 Rust의 메모리 안전성 방식이 맞지 않아 사용 패턴에도 제약이 있었음
C 타입이 Rust 트레이트를 구현할 수 없어, 사용자 정의 C 키 타입으로 Hash
와 Eq
를 요구하는 HashMap::get
을 사용하는 경로도 막혔음
이 실험은 첫 단계의 상당 부분을 해결했지만, Rust가 다른 언어의 구현을 다시 호출하는 문제 는 남겼음
rustc를 라이브러리로 사용하되 MIR는 피하기
새 접근은 외부 명령으로만 실행하는 대신 rustc_driver::run_compiler
를 이용해 rustc를 라이브러리로 실행 함
Rust 컴파일러의 비공개이며 불안정한 API에 의존하는, 공식적으로 지원되지 않는 방식임
현재 구현도 rustc를 두 번 호출하지만 이전 실험과는 다른 방식으로 연결함
rustc_args
로 사용할 Rust 라이브러리를 지정하고, 콜백을 통해 의존성 정보가 준비되는 시점을 파악함
Valen 소스의 import rust.glass_domino.Window
같은 구문을 읽어 use glass_domino::Window;
가 들어 있는 lib.rs
를 생성함
이 파일 경로와 --extern
의존성 경로를 rustc 인수에 추가하며, 의존성 경로 처리는 Cargo가 맡음
after_expansion
콜백 이후 Valen의 타입 검사 단계가 Rust 크레이트 정보를 질의할 수 있음
Valen 코드를 Rust MIR로 변환하는 접근은 필요한 기능을 표현하지 못함
MIR의 가변 참조는 유일해야 하므로, 공유 가능한 가변 참조를 허용하는 그룹 빌림과 맞지 않음
메모리 읽기/쓰기 명령에 LLVM의 별칭 그룹이나 !alias.scope
를 지정할 수 없음
목표로 하는 comptime
메타프로그래밍을 표현할 수 없음
따라서 Valen은 자체 IR와 백엔드 를 유지하고 rustc와 협력해야 함
두 컴파일러의 단형화 협력
단형화(monomorphization) 는 foo<T>
같은 제네릭 함수를 실제 사용 타입에 따라 foo<i32>
, foo<bool>
, foo<String>
등으로 구체화하는 과정임
인스턴스화라고도 부르며, 드물게는 정교화(elaboration)라고 부름
예제에서는 Valen의 main
이 rust_func<MyStruct>
를 호출하고, Rust 함수가 다시 Valen의 트레이트 구현 을 호출함
MyStruct
는 Rust의 RustTrait
을 구현함
Rust 함수는 상수 제네릭 인수 4와 6으로 method
를 각각 호출함
valenc
가 main
을 단형화하고, rustc
가 rust_func<MyStruct>
를 단형화한 뒤, valenc
가 MyStruct.method<4>
와 <6>
을 구체화해야 함
실제 구현에서 valenc
가 rustc의 단형화기를 직접 호출하지는 않음
Valen 함수를 단형화한 뒤 추가로 단형화할 Rust 함수 목록 을 rustc에 반환함
이 방식으로 Rust 포크에 필요한 패치 하나를 줄였음
현재 설계와 rustc 패치
현재 설계는 빈 Rust 함수와 콜백 가로채기 로 두 컴파일러를 연결함
의존성 정보가 준비되면 Valen이 Rust 정보를 조회하고, main
같은 외부 공개 Valen 함수에 대응하는 빈 Rust 함수를 생성함
rustc가 이 빈 MIR 함수를 단형화하려 할 때 가로채 Valen이 처리하고, 그 함수가 호출하는 다른 Rust 함수도 알려줌
rustc의 LLVM 백엔드가 빈 함수를 변환하려 할 때 다시 가로채 Valen의 LLVM 백엔드 가 대신 코드를 생성함
마지막 두 콜백은 rustc에 없어 약 100줄의 패치 로 추가했음
한 패치는 rustc가 LLVM을 사용한다고 가정하지만, Rust에는 GCC와 Cranelift 백엔드도 있음
현재 형태는 작동하더라도 업스트림에 반영할 수 없는 구현이며, 다음 버전을 위한 더 나은 접근을 구상하고 있음
이 기능을 활용하도록 Valen 컴파일러 구조를 다시 설계 하는 데 2026년 대부분을 투입함
Carbon 팀도 Rust 라이브러리 재사용에 관심을 보였으며, 이 통합 작업에 함께할 기여자를 찾고 있음
첫 렌더링으로 연결을 확인
검증 대상으로 화면 공간 굴절(screen-space refraction) 을 실험하는 작은 게임 엔진 을 선택함
초기에는 Valen의 main
에서 직접 while
이벤트 루프 를 돌며 tick()
으로 입력을 받고 카메라를 조작하려 했음
하지만 해당 wgpu 기반 앱에서는 이벤트 루프를 직접 소유하는 방식이 맞지 않아, Rust가 Valen으로 되돌아오는 콜백까지 범위를 확장함
MainLoopCallback
을 구현하는 Valen 클로저를 Rust의 main_loop
에 전달하는 구조를 완성해 첫 렌더링에 성공 함
반년이 넘는 설계, 구현, 리팩터링, 구조 개편 끝에 두 컴파일러가 연결됨
다만 렌더링에는 반투명 굴절 잔디 같은 시각적 이상이 남아 있음
경계를 넘는 메모리 안전성과 현재 제약
그룹 빌림 은 공유 가능한 가변 참조를 허용하는 더 유연한 빌림 검사 방식임
신중하게 설계하면 Rust 빌림 검사의 상위 집합이 되어, 언어 경계를 넘어서도 검사할 수 있음
현재 프로토타입은 일부 경계 검사를 수행하지만 완성되지는 않았음
다음 세 가지 이점은 아직 이론적이며 입증되지 않음
그 외에는 불변인 객체 내부로 가변 참조를 만들 수 있음
Rc
가 RefCell
이나 Cell
없이 가변 객체를 담을 수 있음
일부 사례에서 런타임 성능이 더 빨라질 수 있음
성능 기대의 근거는 Rust 예제에 필요한 추가 entities.get_mut(id)
조회 두 번 과 오류 처리 분기를 피할 가능성임
아직 구현과 벤치마크로 검증하지 않았음
LLVM의 noalias
와 alias.scope
정보를 표현해 기존 최적화를 유지할 수 있을 것으로 기대하며, 별도 설계 문서 에 관련 구상이 있음
현재 구현에는 다음 제한이 있음
Valen 자체의 선형 타입은 있지만, 기존 Rust 타입을 선형 타입으로 선언할 수는 없음
그룹 빌림과 언어 경계의 빌림 검사는 작동하지만 클로저는 제외 됨
Rust 트레이트를 구현하는 Valen 구조체를 포함해 구조체가 경계를 넘을 수 있지만, 크기가 0인 구조체만 지원 함
필드가 있는 구조체는 별도 프로토타입에서 작동할 뿐 Valen에는 아직 통합되지 않음
세대별 참조는 일시적으로 비활성화되어 있으며 복구를 희망하고 있음
Vale가 아닌 Valen으로 출발하는 이유
기존 Vale 는 세대별 참조와 영역 빌림(region borrowing)을 결합한, 고수준이면서 고성능인 언어였음
시스템 프로그래밍 언어보다 엄격한 메모리 안전성 제약 덕분에 완전한 재현 가능성 같은 기능을 구현할 수 있었음
Vale의 선형 타입은 Mojo가 이를 도입하는 데 직접적인 영향을 주었음
Valen 은 세대별 참조와 영역 빌림의 조합 대신, 순수 컴파일 타임 메모리 안전성 방식인 그룹 빌림을 사용함
시스템 프로그래밍 언어로서 더 큰 자유와 그룹 빌림을 통한 속도를 추구함
Rust처럼 unsafe
를 둘 예정이어서 Vale만큼 엄격하게 안전하지는 않을 것임
Vale의 메모리 모델은 이미 제약 참조(constraint references) , 일반적인 세대별 참조, 확률적 세대별 참조와 영역 빌림의 조합으로 바뀌어 왔음
다시 같은 이름으로 모델을 바꾸기보다 별도 언어 이름 을 사용하기로 함
앞으로 그룹 빌림과 선형 타입의 결합 , 경계를 넘는 빌림 검사, 컴파일러 재설계의 세부 내용을 더 공개할 예정임
댓글과 토론
AI 자동 생성 콘텐츠
본 콘텐츠는 GeekNews의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기