# Zig, 새로운 @bitCast 의미론과 LLVM 백엔드 개선

> Clean Markdown view of GeekNews topic #30834. Use the original source for factual precision when an external source URL is present.

## Metadata

- GeekNews HTML: [https://news.hada.io/topic?id=30834](https://news.hada.io/topic?id=30834)
- GeekNews Markdown: [https://news.hada.io/topic/30834.md](https://news.hada.io/topic/30834.md)
- Type: GN+
- Author: [neo](https://news.hada.io/@neo)
- Published: 2026-06-26T09:09:21+09:00
- Updated: 2026-06-26T09:09:21+09:00
- Original source: [ziglang.org](https://ziglang.org/devlog/2026/?2026-06-25#2026-06-25)
- Points: 1
- Comments: 1

## Topic Body

- Zig **master 브랜치**에 LLVM 백엔드의 비 ABI 정수 처리 개선과 새 `@bitCast` 의미론이 병합되어, 최적화 문제와 언어 동작 불일치를 함께 정리함
- `u4`, `i13`, `u40` 같은 **임의 비트폭 정수**는 SSA 값에서는 bit-int로 다루되, 메모리 저장 시 ABI 크기 정수로 확장하는 방식으로 바뀜
- 기존 `@bitCast`는 메모리 바이트 재해석에 가까웠지만, 새 정의는 타입의 **논리적 비트 배열**을 기준으로 해석해 endian 의존성을 줄임
- 변경은 LLVM·C 백엔드와 `comptime` 실행까지 확장됐고, 표준 라이브러리·컴파일러·`compiler_rt`의 관련 사용처도 함께 점검됨
- 놓치던 LLVM 최적화가 되살아나면서 Zig 컴파일러 자체에서 약 **5% 성능 개선**이 관찰됐고, 0.17.0에서 일부 런타임 성능 향상을 기대할 수 있음

---

### LLVM 백엔드의 임의 비트폭 정수 처리 변경
- Zig는 기존에 `u4`, `i13`, `u40` 같은 **임의 비트폭 정수 타입**을 LLVM IR의 bit-int 타입인 `i4`, `i13`, `i40`으로 직접 lowering해왔음
- 이 방식은 LLVM의 메모리 표현 의미론이 최적화기에 불필요한 제약을 만들었고, Clang이 이런 LLVM IR을 만들지 않아 LLVM 내부 경로도 충분히 테스트되지 않았음
- 지난 몇 년 동안 실제로 **최적화 누락**과 **miscompilation** 사례가 관찰됨
  - [trivial optimizations being missed](https://github.com/ziglang/zig/issues/17768)
  - [miscompilations](https://codeberg.org/ziglang/zig/issues/35560)
- 새 방식은 SSA 값 조작에는 bit-int 타입을 유지하되, 메모리에 저장할 때 `i8`, `i16`, `i32` 같은 **ABI 크기 타입**으로 zero-extend 또는 sign-extend함
- 이 lowering은 C의 `_BitInt(N)`를 Clang이 lowering하는 방식과 맞아, LLVM에서 더 잘 지원되는 경로로 기대됨

### 기존 `@bitCast`의 한계
- 기존 `@bitCast`는 개념적으로 다음 동작에 가까웠음
  - 피연산자 값의 포인터를 얻음
  - 그 포인터를 목적지 타입 포인터로 캐스팅함
  - 해당 포인터에서 값을 로드함
- 즉 기존 정의는 타입의 논리 구조보다 메모리의 **바이트 재해석**에 가까웠음
- 시간이 지나며 실제 동작은 이 정의에서 벗어났고, 대부분의 타깃에서 `@sizeOf(u24)`가 `@sizeOf([3]u8)`보다 큰데도 `[3]u8`을 `u24`로 `@bitCast`하는 것이 허용됨
- LLVM 백엔드는 충분히 명세화되지 않은 `@bitCast` 의미론을 구현하고 있었고, 정수 타입의 메모리 저장 방식을 바꾸자 컴파일러 테스트 스위트에서 Illegal Behavior와 크래시가 발생함
- LLVM 백엔드에 기존 동작을 흉내 내는 로직을 추가하는 대신, 새로운 `@bitCast` 정의를 전반적으로 구현하는 방향이 선택됨

### 새 `@bitCast` 의미론
- 새 의미론은 2024년에 제출되어 수락된 언어 제안 [#19755](https://github.com/ziglang/zig/issues/19755)를 기반으로 함
- 이 의미론은 이미 self-hosted x86_64 백엔드에 구현되어 있었고, 이번 변경으로 LLVM·C 백엔드와 `comptime` 실행까지 확장됨
- 새 `@bitCast`는 메모리 바이트가 아니라 타입을 **논리적으로 표현하는 비트 순서**를 기준으로 동작함
  - `u5`는 least-significant bit부터 most-significant bit까지 5개의 논리 비트로 구성됨
  - `[2]u5`는 첫 번째 원소의 5비트 뒤에 두 번째 원소의 5비트가 이어진 10개의 논리 비트로 구성됨
- `u8`을 같은 크기의 `i8`로 바꾸는 경우처럼 단순한 정수 간 변환은 비트가 그대로 유지되고, 최상위 비트가 부호 비트로 해석됨
- 정수 타입과 `packed struct` 또는 `packed union` 사이의 `@bitCast` 의미론도 유지됨

### 배열·벡터에서 달라지는 동작
- 새 의미론이 기존과 달라지는 지점은 **배열과 벡터** 같은 aggregate 타입이 관련될 때임
- 예를 들어 `[2]u8`을 `u16`으로 `@bitCast`하면 기존 의미론에서는 대상 endian에 따라 결과가 달랐음
  - big-endian 타깃에서는 첫 번째 배열 원소가 상위 8비트가 됨
  - little-endian 타깃에서는 첫 번째 배열 원소가 하위 8비트가 됨
- 새 의미론은 논리적 비트 표현만 고려하므로 endian에 독립적이며, 모든 타깃에서 첫 번째 배열 원소가 하위 8비트가 됨
- 일반적으로는 little-endian 타깃에서의 기존 동작과 더 가까움
- `[2]u3`을 `@Vector(3, u2)`로 변환하는 것처럼 비정형적인 변환도 가능함
  - 배열의 논리 비트를 이어 붙인 뒤 2비트 단위로 읽어 벡터 원소를 구성함
  - 정수를 `@Vector(n, u1)`로 `@bitCast`해 개별 비트 벡터로 분해하는 용도에도 쓸 수 있음

### 함께 반영된 제안과 마이그레이션
- 이번 작업 중 `@bitCast`와 관련된 작은 수락 제안도 함께 구현됨
  - 포인터 벡터와의 `@bitCast` 금지: [#18936](https://github.com/ziglang/zig/issues/18936)
  - enum에 대한 `@bitCast` 허용: [#35602](https://codeberg.org/ziglang/zig/issues/35602)의 일부
- 새 의미론은 기존 의미론과 의미 있게 다르기 때문에 표준 라이브러리, 컴파일러, `compiler_rt` 같은 지원 라이브러리의 `@bitCast` 사용이 점검됨
- 관련 PR은 [codeberg.org/ziglang/zig/pulls/35711](https://codeberg.org/ziglang/zig/pulls/35711)이며, master에 병합되면서 여러 이슈도 함께 닫힘
- 변경된 의미론과 권장 마이그레이션 절차는 Zig **0.17.0 릴리스 노트**에 정리될 예정임

### 0.17.0에서 기대되는 성능 효과
- 원래 목표였던 LLVM 백엔드의 비 ABI 정수 lowering 변경은 놓치던 최적화를 되살리는 데 성공함
- 관련 결과는 [demonstrably successful](https://github.com/ziglang/zig/issues/17768#issuecomment-4787726124)로 확인할 수 있음
- Zig 컴파일러 자체는 내부적으로 임의 비트폭 정수를 많이 쓰지 않는데도, 더 나은 최적화 덕분에 약 **5% 성능 개선**을 보임
- 0.17.0에서는 일부 코드에서 작은 런타임 성능 향상이 생길 수 있음

## Comments



### Comment 60330

- Author: neo
- Created: 2026-06-26T09:09:22+09:00
- Points: 1

###### [Lobste.rs 의견들](https://lobste.rs/s/uge7mm/new_bitcast_semantics_llvm_backend) 
- 글에서 말하는 **논리적 비트 표현**이 엔디언 독립적이라고 하지만, 실제 설명은 빅엔디언 비트 순서나 바이트 순서를 지원하지 않는 명백한 **리틀엔디언** 방식으로 보임
  - 여기서 **엔디언 독립적**이라는 뜻은 리틀엔디언/빅엔디언 아키텍처 사이에서 동작이 달라지지 않는다는 의미로 보임

- 2026년 6월 25일자 새 개발 로그로, 새 **`@bitCast` 의미론**과 LLVM 백엔드 개선이 [최근 풀 리퀘스트](https://codeberg.org/ziglang/zig/pulls/35711)에 병합됐다는 내용임

- 흥미롭지만, 드물게 테스트되는 **빅엔디언 대상**에서 아래처럼 작성된 코드가 갑자기 깨질 수 있지 않을까 싶음  
  비-Zig 의사코드로 쓰면:  
  ```text  
  if target_is_little_endian {  
      my_int = @bitCast(my_array);  
  } else {  
      my_int = @bitCast([my_array[1], my_array[0]]);  
  }  
  ```
  - 나도 그 생각을 했지만, 결국 피할 수 없는 변경을 미루면 문제가 더 커질 뿐이라고 봄  
    실제로 큰 문제는 아닐 듯한데, Zig 저장소의 **수천 개 `@bitCast`** 중 이 변경의 영향을 받은 건 100개보다 훨씬 적었던 것 같음  
    솔직히 배열/벡터와 스칼라 사이 변환에서 `@bitCast`가 어떻게 동작하는지 대부분의 Zig 사용자가 정확히 알고 있었다고도 생각하지 않음. 기존에는 작성자 시스템에서만 테스트되어 리틀엔디언에서만 동작하던 코드가 이제는 어디서나 동작하게 되는 경우도 많을 듯함

- 예전 C 프로그래머로서, C의 **비트 필드**는 아키텍처마다 동작이 이식 가능하지 않아 별로 인기가 없었던 걸로 기억함  
  새 Zig `@bitCast` 의미론은 서로 다른 아키텍처에서도 같은 결과를 주는 **이식 가능한 추상 의미론**이라서 딱 필요했던 방향이라고 봄  
  최근 내 언어의 비트 필드와 비트 캐스트 설계를 하고 있어서, 내 코드가 어떻게 동작해야 할지 명확히 하려고 Zig 설계와 구현 문서를 더 자세히 볼 생각임
  - C 비트 필드에 대한 Zig의 주된 대안은 아마 **`packed struct`** 와 **`packed union`** 이고, 둘 다 새 `@bitCast` 정의와 잘 맞도록 정의되어 있음  
    `packed struct`는 필드의 비트를 “기반 정수”에 채워 넣는 방식임. 예를 들어 필드가 `bool`, `u6`, `i9`이고 기반 정수가 `u16`이면, `u16`의 최하위 비트가 `bool`, 다음 6비트가 `u6`, 나머지 9비트가 `i9`가 됨. 즉 Zig의 packed struct는 여러 시프트와 마스크 위에 얹힌 문법 설탕에 가까움  
    `packed union`도 기반 정수를 가지지만, 모든 필드가 기반 정수와 정확히 같은 비트 수를 써야 함. 그래서 한 필드에 저장하고 다른 필드에서 읽는 동작은 새 의미론의 `@bitCast`와 거의 동일함. 다만 `packed union`/`packed struct` 필드는 배열이나 벡터 타입을 가질 수 없음  
    개인적으로는 이런 도구들이 “비트 관련 구조”를 표현하기에 잘 맞는다고 봄. 여러 값을 `packed struct`로 비트 패킹해 C 비트 필드처럼 쓸 수 있고, 비트 연산 위의 문법 설탕이라 C에서 타입 안전하지 않은 매크로 더미로 처리하던 **비트 플래그**도 깔끔하게 표현 가능함  
    예를 들어 RWX 접근 플래그는 C에서는 `ACCESS_READ`, `ACCESS_WRITE`, `ACCESS_EXEC` 매크로와 `uint8_t` API로 받을 수 있지만, Zig에서는 `Access = packed struct(u8)`로 `read`, `write`, `exec`, `reserved` 필드를 정의하고 API에서 `Access`를 받을 수 있음  
    `packed struct`와 `packed union`을 쓰면 꽤 이상한 비트 배치도 표현할 수 있음. Mach-O 객체 포맷의 심볼 테이블 엔트리에는 역사적 이유로 보이는 특이한 `n_type` 필드가 있는데, 이를 `packed union(u8)` 안에 `bits: packed struct(u8)`와 `stab: enum(u8)` 형태로 모델링할 수 있음  
    이 `n_type` 값을 다룰 때 수동 시프트나 마스킹이 필요 없음. `n_type.bits.is_stab != 0`을 확인하고 참이면 `n_type.stab`으로 `switch`하면 되고, 아니면 `n_type.bits`의 다른 필드를 보면 됨. 반대로 `.{ .stab = .gsym }`나 `.{ .bits = .{ .ext = false, .type = .undf, .pext = false, .is_stab = 0 } }`처럼 값을 만들 수도 있음  
    원 글 주제와는 다른 언어 기능으로 조금 길어졌지만, 새 언어 설계에 참고할 만한 걸 찾는다면 Zig의 `packed struct`와 `packed union`을 직접 써보면 좋겠음. 단순하지만 꽤 괜찮은 도구라고 봄
