wasm2js 컴파일러, 재현 가능한 빌드를 향한 여정
WebAssembly(WASM)를 JavaScript로 컴파일하는 과정에서 재현 가능한 빌드(Reproducible Builds)의 어려움이 드러남
컴파일러의 내재된 비결정성(Inherent Nondeterminism)으로 인해 동일 입력에도 다른 출력 바이트가 생성되는 문제 발생
wasm2js 도구 버전 관리 및 LLVM의 아키텍처별 주소 레이아웃 차이가 주요 난관으로 작용함
CI/CD 환경에서의 검증을 통해 빌드 재현성을 확보하려는 노력이 있었음
컴파일러의 비결정성(Nondeterminism)과 빌드 재현성 문제
커뮤니티에서는 컴파일러가 이론적으로는 결정론적 함수(Deterministic Function)여야 하지만, 실제로는 소스 코드 외 다양한 요인으로 인해 비결정적 출력을 생성한다고 지적합니다. 특히 C/C++ 개발에서 `__DATE__` 및 `__TIME__` 매크로 사용은 빌드 시점의 시간 정보가 포함되어 동일한 소스 코드라도 다른 바이너리를 생성하는 대표적인 예시로 언급됩니다. 이는 재현 가능한 빌드(Reproducible Builds)를 달성하는 데 있어 근본적인 장애물로 작용합니다.
wasm-opt의 자동 실행과 버전 충돌
논의에 따르면, clang 컴파일러는 백그라운드에서 `wasm-opt` 도구를 자동으로 호출하여 WebAssembly 출력을 최적화합니다. 그러나 배포판에서 제공하는 `wasm-opt`의 오래된 버전과 개발 환경의 최신 버전 간의 차이가 빌드 결과의 불일치를 야기했습니다. 특히 WebAssembly 예외 처리(WASM Exceptions) 지원 여부 및 버전 차이가 문제를 심화시켰으며, 이를 해결하기 위해 `--no-wasm-opt` 옵션을 사용하거나 특정 버전을 번들링하는 방안이 제시되었습니다.
주소 레이아웃(Address Layout) 차이로 인한 빌드 불일치
특히 LLVM 컴파일러의 아키텍처별 주소 레이아웃 차이는 빌드 재현성을 저해하는 또 다른 요인으로 지목됩니다. clang이 예외 처리 경로에서 포인터 값의 순서에 따라 try_table 블록의 출력 순서가 달라지며, 이는 약 29바이트의 차이를 발생시킵니다. 이 문제를 해결하기 위해 `setarch --addr-no-randomize`를 사용하여 주소 공간 랜덤화를 비활성화하고, 아키텍처별로 알려진 SHA256 체크섬을 생성하여 CI에서 검증하는 방식을 채택했습니다.
LLVM의 아키텍처 간 재현성 문제와 향후 과제
참여자들은 LLVM 자체의 아키텍처 간 빌드 재현성 문제를 지적하며, 이는 근본적으로 LLVM의 업스트림 버그(Upstream Bug)일 가능성이 있다고 언급합니다. 현재로서는 동일 아키텍처 내에서의 빌드 재현성은 확보되었으나, 서로 다른 아키텍처(예: x86_64와 arm64) 간의 완벽한 재현성은 아직 해결되지 않은 과제로 남았습니다. 향후 LLVM 개발팀이 시드(Seed) 값을 설정하여 이러한 반복 순서 문제를 해결해주기를 기대하는 의견이 있습니다.