Zig 0.17.0 개발 업데이트: @bitCast 의미론, LLVM 백엔드 및 빌드 시스템

Zig 0.17.0 개발 업데이트: @bitCast 의미론, LLVM 백엔드 및 빌드 시스템

Zig는 성능, 예측 가능성 및 개발자 경험을 개선하기 위해 핵심 컴파일러와 빌드 인프라를 진화시키고 있습니다. 주요 업데이트에는 엔디언에 구애받지 않는 @bitCast의 근본적인 재정의, LLVM 백엔드에서의 임의 너비 정수의 더 효율적인 로워링(lowering), 그리고 zig build 프로세스의 중대한 아키텍처 재작업이 포함됩니다.

새로운 엔디언 불가지론적 @bitCast 의미론

Zig는 @bitCast가 메모리의 원시 바이트를 재해석하는 대신 타입의 논리적 비트 표현을 기반으로 작동하도록 @bitCast를 재정의했습니다. 이 변경 사항은 비트캐스팅이 다양한 타겟 아키텍처에서 동일하게 동작하도록 보장하여, 이전의 타겟 엔디언 의존성을 제거합니다.

논리적 비트 레이아웃

새로운 의미론 하에서, @bitCast를 지원하는 모든 타입은 "논리적 비트 레이아웃"—즉, 정렬된 비트 시퀀스를 가집니다. 예를 들어:

  • 정수: u5는 최소 유의 비트(least-significant)에서 최대 유의 비트(most-significant) 순으로 정렬된 5개의 논리적 비트를 구성합니다.
  • 배열: [2]u5는 10개의 논리적 비트(첫 번째 요소의 5개 비트 뒤에 두 번째 요소의 5개 비트가 이어짐)로 구성됩니다.

집합 타입에 미치는 영향

가장 중요한 변화는 배열이나 벡터와 같은 집합 타입(aggregate types)을 비트캐스팅할 때 발생합니다. 이전에는 [2]u8u16으로 비트캐스팅하면 빅 엔디언과 리틀 엔디언 타겟에서 서로 다른 값이 결과로 나왔습니다. 이제 이 작업은 엔디언에 구애받지 않습니다. 즉, 배열의 첫 번째 요소가 결과 정수의 하위 8개 비트가 됩니다.

추가적인 @bitCast 변경 사항

  • 열거형(Enums): 이제 열거형에 대해서도 @bitCast가 허용됩니다.
  • 포인터(Pointers): 포인터의 벡터로 또는 포인터 벡터로부터의 @bitCast는 이제 금지됩니다.

LLVM 백엔드 개선 및 성능

Zig는 LLVM의 네이티브 비트-정수 타입이 메모리에서 가질 수 있는 최적화 저하 및 오컴파일(miscompilation) 문제를 피하기 위해, 임의 비트 너비 정수 타입(예: u4, i13)을 LLVM 백엔드가 처리하는 방식을 업데이트했습니다.

정수 로워링

이전에는 Zig가 이러한 타입들을 LLVM IR의 비트-정수 타입으로 직접 로워링했습니다. 새로운 구현은 SSA 형태의 값에 대해서만 비트-정수 타입을 사용하며, 메모리에 저장할 때는 ABI 크기 타입(i8, i16, i32 등)으로 확장합니다. 이 방식은 Clang이 C의 _BitInt(N)을 로워링하는 방식과 일치하며, LLVM 내에서 더 나은 지원과 더 신뢰할 수 있는 최적화를 보장합니다.

성능 이득

이 변경 사항은 이미 누락된 최적화를 복구하는 데 성공했음을 보여주었습니다. Zig 컴파일러 자체에서 약 5%의 성능 향상이 관찰되었으며, 이는 다가오는 0.17.0 릴리스에서 사용자들에게 유사한 런타임 성능 향상을 제공할 것임을 시사합니다.

빌드 시스템 아키텍처 재작업

zig build의 속도를 높이기 위해, 빌드 시스템은 이제 "설정자(configurer)" 프로세스와 "제작자(maker)" 프로세스를 분리합니다.

설정자와 제작자의 분리

  • 설정자(The Configurer): build.zig 파일은 디버그 모드에서 작은 프로세스로 컴파일됩니다. 이는 메모리 내에서 빌드 그래프를 빌드하고 이를 바이너리 설정 파일로 직렬화합니다.
  • 제작자(The Maker): 부모 zig build 프로세스는 릴리스 모드로 "제작자" 프로세스를 비동기적으로 컴파일합니다. 이 제작자 프로세스는 직렬화된 빌드 그래프를를 실행합니다.

새로운 아키텍처의 이점점

  1. 컴파일 감소: 변경 사항이 발생했을 때 전체 빌드 시스템이 아닌 사용자의 build.zig 로직만 컴파일됩니다.

  2. 캐시된 설정: 설정이 변경되지 않았다면(예: -freference-trace와 같은 CLI 플래그를 추가하는 경우), 빌드 시스템은 build.zig 로직을 다시 실행하는 것을 완전히 건너뛸 수 있습니다.

  3. ** optimized execution**: 빌드 그래프를 실행하는 프로세스는 이제 최적화가 활성화된 상태로 컴파일됩니다.

벤치마크 결과, 단순한 명령의 실행 시간(wall-clock time)이 극적으로적으로 감소했습니다. 예를 들어, zig build -h는 150ms에서 14.3ms로 단축되었습니다.

기타 주목할 만한 컴파일러 및 툴링 업데이트

ELF 링커 및 증분 컴파일

  • 새로운 ELF 링커: 새로운 ELF 링커( -fnew-linker를 통해 활성화)는 이제 LLVM 및 LLD 라이브러리를 사용하여 셀프 호스티드 Zig 컴파일러를 빌드할 수 있습니다. 이는 x86_64 Linux에서 빠른 증분 재빌드(incremental rebuilds)를 지원하여, 일부 재빌드 시간을 30ms까지 낮출 수 있습니다.
  • LLVM 증분 지원: 이제 LLVM 백엔드와 함께 증분 컴파일이 작동하여, 개발자가 컴약린러 에러러를 초 단위가 아닌 밀리초 단위로 즉시 확인할 수 있게 합니다.

타입 해상도 재설계

  • 컴파일러의 내부 타입 해상도는 이제 더 게으른(lazy) 방식으로 작동합니다. 만약 어떤 타입이 네임스페이스로만 사용되고 초기화되지 않는다면, 컴파일러는 더 이상 해당 타입의 필드를 분석하지 않으며, 이를 통해 사용되지 않는 필드에서의 불필요한 @compileError 발생을 방지합니다.
  • 의존성 루프 에러 메시지는 루프가 정확히 어디서 시작되는지 상세한 노트를 제공하도록 재설계되었습니다.

Windows 네이티브 API 선호

  • Zig 표준 라이브러리는 불필요한 힙 할당, 비대해짐, 비결정론적 실패 모드를 피하기 위해 Win32 래퍼(kernel32.dll)보다 네이티브 API(ntdll.dll)를 선호하는 방향으로 이동하고 있습니다. 이는 특히 엔트로피(entropy) 구현( bcryptprimitives.dll을 피함) 및 파일 I/O (NtReadFile/NtWriteFile)에서 두드러집니다.

패키지 관리

  • 로컬 저장소: 가져온 패키지는 이제 프로젝트 로컬의 zig-pkg 디렉토리 내에 저장되어, 오프라인 빌드와 더 쉬운 IDE 통합을 용이하게 합니다.
  • 프로젝트 오버라이드: --fork=[path] 플래그그를 통해 개발자들은 build.zig.zon을 수정하지 않고도 로컬 소스 체크아웃을 통해 의존성을 일temporarily override할 수 있습니다.

커뮤니티 관점

대부분의 기술 커뮤니티는 이러한 심층적인 분석을 가치 있게 여기지만, 새로운 @bitCast 의미론에 대해서는 일부 논쟁이 있습니다. 한 사용자는 다음과 같이 언급했습니다:

"이것은 엄청난 실수입니다. bitCast 같은 것이 이렇게 동작할 것이라고는 전혀 예상할 수 없습니다... 왜 이렇게 단순하고 저수준의 것을 복잡하고 고수준의 것으로 바꾸는 거죠?"

반대로, 다른 사용자들은 수동적인 비트 조작(bit-twiddling) 없이 비트가 팩킹된 바이너리 헤더를 처리하는 데 있어 이러한 변화의 유용성을 강조했습니다.

Sources