2026년 6월 26일, Zig 개발 로그에 “SPIR-V Backend Progress”라는 제목의 글이 올라왔다. 저자는 Ali Cheraghi, Zig 컴파일러 SPIR-V 백엔드의 핵심 기여자다. 이것은 “SPIR-V 백엔드가 이제 사용 가능합니다”라고 선언하는 이정표적인 글이 아니다 — 오히려 그 반대로, 상당 부분을 bitrot(코드 부패), 단일 스레드 제한, 그리고 고작 49%의 동작 테스트 통과율을 인정하는 데 할애했다. 그런데 같은 날 이 글이 Lobsters 메인 페이지에 28점으로 올랐고, 세 개의 댓글은 모두 흥분을 표현하고 있었다.
필자는 이 흥분의 출처를 이해하려 시도했다. 자체 호스팅 컴파일러 백엔드 하나가 동작 테스트 통과율 반도 안 되고, 메인 브랜치에 병합된 후 여러 곳이 깨져 수 주간의 수정이 필요했다 — 어떤 전통적인 소프트웨어 납품 기준으로 봐도 이것은 “초기 실험”으로 분류되어야 마땅하다. 그러나 커뮤니티는 여기서 완전히 다른 신호를 읽어냈다: 시스템 프로그래밍 언어가 GPU 영역에 교두보를 구축하기 시작했다.
SPIR-V는 어디에 위치하는가
이 신호를 이해하려면, 먼저 SPIR-V가 GPU 생태계 안에서 차지하는 위치로 돌아가야 한다.
SPIR-V는 Khronos 그룹이 정의한 바이너리 중간 표현(IR)으로, Vulkan, OpenCL, OpenGL을 지원하며, 머지않아 DirectX에서도 소비될 것이다. 그 핵심 설계 목표는 단순하다: 셰이더/컴퓨트 커널의 컴파일을 디바이스 드라이버에서 꺼내 애플리케이션 측에 두는 것이다. SPIR-V 이전에 GPU 프로그래밍의 표준 경로는 — GLSL이나 HLSL로 소스 텍스트를 작성하고, 드라이버에 넘겨 런타임에 컴파일하는 것이었다. 드라이버 안의 컴파일러 품질은 제각각이었고, 다른 벤더, 다른 드라이버 버전에서 컴파일 결과가 달라질 수 있었다. SPIR-V는 이 분업을 바꿨다: 언어 프론트엔드가 규격에 맞는 SPIR-V 바이너리를 생성할 책임을 지고, 드라이버는 그것을 GPU ISA로 번역하는 역할만 맡는다. 컴파일러의 책임이 드라이버에서 언어 도구 체인으로 이동한 것이다.
이 한 걸음의 이동이 의미하는 바: 규격에 맞는 SPIR-V를 생성할 수 있는 어떤 컴파일러 프론트엔드라도 GPU 프로그래밍의 진입점이 될 수 있다. 더 이상 GLSL이 필요하지 않다. 더 이상 HLSL이 필요하지 않다. Vulkan 스펙 자체는 당신의 SPIR-V 바이너리가 GLSL에서 번역되었는지, C++에서, Rust에서, Julia에서 — 또는 Zig에서 — 직접 컴파일되었는지 신경 쓰지 않는다.
이것이 바로 컴파일러 백엔드가 SPIR-V를 출력할 수 있다는 것이 그토록 중요한 이유다. 범용 프로그래밍 언어의 컴파일러가 직접 GPU 코드를 생성할 수 있다 — 셰이더 언어와 범용 언어의 경계가 흐려지기 시작한다.
Zig의 백엔드는 어디까지 왔나
2026년 6월 26일의 개발 로그는 다섯 개 차원의 진전을 다룬다. 필자는 엔지니어링 중요도 순으로 정렬한다:
첫째, @SpirvType 내장 명령어. SPIR-V에는 Zig 타입 시스템과 직접 대응되지 않는 타입들이 있다 — 샘플러(sampler), 이미지(image), 샘플 이미지(sampled image), 런타임 배열(runtime array). 과거에는 이러한 타입들을 인라인 어셈블리로 SPIR-V 명령어를 손수 작성해야 표현할 수 있었으며, 이는 오랫동안 “셰이더 작성의 최대 장벽”으로 지목되어 왔다. @SpirvType은 GPU 전용 타입을 컴파일러가 인식하는 1등 개념으로 직접 격상시킨다. 코드 안에서 Zig 문법으로 샘플러를 선언하고, 디스크립터 셋과 바인딩 포인트에 연결할 수 있다 — 이것은 “SPIR-V 명령어를 생성할 수 있다”에서 “Zig 안에서 자연스럽게 셰이더를 작성할 수 있다”로의 결정적 도약이다.
둘째, 실행 모드를 호출 규약으로 전환. 워크그룹 크기, 프래그먼트 오리진, 메시 셰이더 파라미터 — 이러한 실행 모드 정보는 과거에 인라인 어셈블리 OpExecutionMode로 수동 삽입되었다. 새 설계에서는 함수의 호출 규약을 callconv(.{ .spirv_kernel = .{ .x = 8, .y = 8, .z = 1 } })로 선언하면, 컴파일러가 올바른 실행 모드를 자동으로 추론한다. 동시에 spirv_task와 spirv_mesh라는 두 가지 새로운 호출 규약이 추가되어 메시 셰이딩 파이프라인을 지원한다. 사용자 측에서 보면, 컴퓨트 셰이더의 진입 함수를 선언하는 것이 일반적인 export Zig 함수를 선언하는 것만큼 자연스러워진다.
셋째, 멀티스레드 코드 생성. SPIR-V 백엔드는 첫날부터 링커 스레드 안에서 단일 스레드로 실행되었다. 이번 리팩터링은 이를 컴파일러 통합 MIR → 코드 생성 파이프라인에 통합시켜, 각 코드 생성 작업이 다른 자체 호스팅 백엔드와 마찬가지로 스레드 풀에 스케줄링되도록 한다. 함께 복구된 것은 dedup_types(중복 타입 명령어 병합)와 prune_unused(데드 코드 제거) 두 ISel 패스다 — 이 패스들은 이전 단일 스레드 리팩터링에서 삭제되었으며, 이제 아키텍처 업그레이드로 인해 복구되었다. 사용자에게 미치는 실제 영향은 컴파일 속도지만, 엔지니어링 판단으로는 SPIR-V 백엔드가 아키텍처 차원에서 “특별 관리” 상태를 벗어나 다른 타겟과 동등한 컴파일 유닛이 되었다는 의미다.
넷째, 오브젝트 파일 링크. .spv 파일이 이제 오브젝트 파일 형식으로 인식된다. 여러 .zig 파일(또는 외부 .spv 오브젝트)을 컴파일한 후 SPIR-V 링커가 이를 단일 모듈로 병합할 수 있다. 이는 대규모 셰이더 프로젝트를 여러 컴파일 유닛으로 분할할 수 있으며, 이론적으로 증분 컴파일과 라이브러리 배포를 지원한다는 것을 의미한다 — 현재 이러한 고급 워크플로가 아직 준비되지 않았지만, 포맷 레벨의 기반은 마련되었다.
다섯째, 능력과 확장을 CPU 기능 세트에서 구동. 과거에는 OpCapability와 OpExtension이 코드 생성이나 인라인 어셈블리에 의해 임시로 삽입되었다. 이제는 SPIRV-Headers에서 의존성 체인을 추출하여 CPU 기능 세트로 통합 관리한다. 어셈블러는 이러한 명령어를 수동으로 삽입하려는 모든 시도를 거부한다 — 컴파일러가 출력의 정확성에 대해 시스템 레벨의 책임을 지기 시작한 것이며, 검증을 다운스트림 spirv-val 도구에 떠넘기지 않게 된 것이다.
4주 전과 비교하면, 동작 테스트 통과율은 약 39%에서 49%(spirv64-vulkan 타겟)로 상승했고, 수십 개의 버그가 수정되었으며, std.gpu는 std.spirv로 개명되었다. Cheraghi 자신의 표현은 절제되어 있다: “SPIR-V 백엔드는 한 달 전보다 의미 있게 더 사용하기 좋아졌지만, 아직 멀었다.”
경쟁 지도 속에 놓고 보기
Zig만이 범용 언어에서 GPU로의 통로를 뚫으려는 유일한 프로젝트는 아니다. 주요 경쟁 프로젝트들을 함께 놓고 보면 Zig SPIR-V 백엔드의 위치를 더 정확히 가늠할 수 있다.
Rust GPU(rust-gpu) 는 가장 직접적인 비교 대상이다. rustc_codegen_spirv에 기반하여 Rust를 SPIR-V 셰이더로 컴파일한다. 프로젝트는 2019년경 시작되어 Embark Studios의 지원과 커뮤니티 이양을 거쳤다. 현재 기본적인 표준 라이브러리 서브셋(spirv-std), 브라우저에서 플레이 가능한 SHADERed 데모, 링크타임 최적화를 위한 실험적 SPIR-T 프레임워크가 존재한다. 그러나 GitHub 이슈와 커뮤니티 논의로 볼 때, Rust 컴파일러 업그레이드가 codegen 플러그인의 추적 적응을 자주 요구하며 안정 버전은 아직 등장하지 않았다.
Circle C++ Shader Compiler 는 표준 C++로 셰이더를 작성할 수 있게 해 주며, 속성 마커로 GPU 진입점을 구분하고 컴파일 산출물은 직접 SPIR-V다. 문법 레벨에서는 CUDA와 유사하다 — 단일 소스, C++ 슈퍼셋. 그러나 Circle은 클로즈드소스 컴파일러이며 Sean Baxter의 개인 유지보수에 의존하므로 생태계 범위가 제한된다.
Julia GPU 는 CUDA.jl과 AMDGPU.jl을 통해 GPU 프로그래밍 능력을 제공하며, 하위 레이어는 SPIR-V를 우회하여 직접 PTX나 AMDGCN 명령어를 생성한다. 강점은 대화형 개발에 있다 — REPL에서 즉시 커널을 작성하고 실행할 수 있다. 약점도 분명하다: 크로스 벤더 이식성이 패키지 생태계에 의존하며 표준 IR에 의존하지 않는다.
Zig SPIR-V 백엔드는 이 지도 위에서 매우 구체적인 위치를 차지한다: SPIR-V를 컴파일러의 자체 호스팅 백엔드로 삼는 유일한 시스템 프로그래밍 언어라는 점이다 — rust-gpu는 Rust 컴파일러의 외부 codegen 플러그인이지 Rust 프로젝트의 1등 구성 요소가 아니며, Circle은 클로즈드소스 개인 프로젝트이며, Julia는 SPIR-V를 우회한다. Zig의 SPIR-V 백엔드는 x86, ARM, RISC-V 백엔드와 동일한 코드 저장소, 동일한 빌드 시스템 안에서 유지보수되며, 동일한 핵심 기여자 그룹에 의해 검토된다.
이것은 양날의 검이다. 컴파일러 메인라인과 동일 저장소에 있다는 것은, SPIR-V 백엔드가 Zig 컴파일러의 매 아키텍처 조정마다 수동적으로 진화해야 한다는 뜻이다 — bitrot은 바로 이 긴밀한 결합의 대가다. 그러나 이것은 컴파일러 인프라에 대한 모든 개선 사항(타입 시스템, 코드 생성 파이프라인, 링커)이 자동으로 SPIR-V 백엔드에 혜택을 줄 수 있다는 뜻이기도 하다. 6월 26일 로그에서 멀티스레드 코드 생성이 복구된 것은 바로 이 메커니즘의 구체적 사례다: 통합 MIR 파이프라인의 아키텍처 결정이 SPIR-V 백엔드에 “무료로” 스레드 풀 스케줄링 능력을 부여한 것이다.
진짜 장벽은 컴파일러 안에 있지 않다
기술 로드맵에서 보면, Zig SPIR-V 백엔드 앞에 놓인 가장 큰 장애물들은 완전히 컴파일러 내부에 있지 않다.
첫 번째 장벽은 주소 공간이다. GPU 메모리 모델은 global, local, private, constant 등 여러 주소 공간을 구분하지만, Zig의 포인터는 기본적으로 generic 주소 공간을 가리킨다고 가정한다. Cheraghi의 블로그는 Vulkan이 OpPtrCastToGeneric을 지원하지 않는다고 언급한다 — 그래서 현재 구현은 모든 포인터를 Function 저장 클래스로 가정하는 임시 방편을 사용한다. 이는 복잡한 포인터 연산(예: 주소 공간을 가로지르는 참조 전달)이 Vulkan 타겟에서 제한된다는 것을 의미한다. OpenCL 타겟에서는 상황이 다소 낫다 — OpenCL 베이스라인 환경이 더 많은 능력을 보장하며, 동작 테스트 통과율도 더 높다(약 75%).
두 번째 장벽은 수치 시맨틱 차이다. Vulkan 환경에서 fma, sqrt, exp, log 등의 명령어는 올바른 반올림을 보장하지 않는다 — 이는 Zig 컴파일러의 기본 수치 시맨틱 가정과 충돌한다. Zig의 결정성에 대한 요구는 셰이더 언어가 수치 정밀도에 대해 가지는 전형적 허용도보다 높다. 이것이 반드시 해결 불가능한 문제는 아니다 — Rust GPU와 GLSL 컴파일러 모두 동일한 시맨틱 간극에 직면한 적이 있다 — 하지만 명시적인 설계 결정과 문서화가 필요하며, 현재도 진행 중이다.
세 번째 장벽은 생태계 차원이다: 표준 라이브러리 적응. GPU 위에는 운영체제도, 파일 시스템도, 힙 할당자도(적어도 전통적 의미에서는) 존재하지 않는다. Zig의 표준 라이브러리에는 이러한 가정에 의존하는 코드가 대량으로 포함되어 있다. std.math, std.sort, 일반적인 데이터 구조와 알고리즘을 GPU 친화적 서브셋으로 이식하는 것은 컴파일러 백엔드 자체에 못지않은 규모의 작업이다. Cheraghi는 “다음 단계” 목록에 프리픽스 합, 리덕션, 행렬 곱셈 등 기초 알고리즘을 나열했다 — 이것들은 HPC와 ML 워크로드의 초석이며, 우선순위 판단이 올바르다는 것을 보여주지만 동시에 진도가 아직 초기 단계임을 말해 준다.
왜 이 로그가 주목을 받았는가
Lobsters의 28점짜리 글로 돌아가자. 기술적 세부 사항을 넘어선 흥분의 원천은 두 가지다.
하나는 시의성 마커다. 같은 날, Zig 개발 로그에는 또 다른 업데이트 — Matthew Lugg가 작성한 “New @bitCast Semantics and LLVM Backend Improvements” — 가 있었고, Lobsters에서는 별도로 16개의 댓글이 달렸다. 하루 만에 Zig 컴파일러 진전 글이 두 개나 올라온 것은 Lobsters 같은 커뮤니티에서 일상적인 일이 아니다. 이것은 언어 생태계의 활성도를 시사한다: Zig 컴파일러가 동시에 여러 차원에서 진전을 보이고 있다 — 정수 디모션, 비트캐스트 시맨틱, LLVM 백엔드 최적화, SPIR-V 백엔드 수정 — 이것은 단일 경로에서만 반복되는 프로젝트가 아니다.
다른 하나는 방향성 신호다. SPIR-V 백엔드의 존재 자체가 이렇게 말하고 있다: Zig 메인테이너들은 시스템 프로그래밍 언어가 GPU 코드를 컴파일할 수 있어야 한다고 생각한다. 이것은 GPU가 시스템 프로그래밍의 정당한 영토임을 선언하는 것이다 — 49%의 통과율은 아직 “우리도 GPU를 지원합니다”라고 말할 수 있는 수준이 아니다.
이 방향은 Rust의 GPU 스토리와는 다르다. Rust의 안전성 철학이 GPU에서 가지는 차별화된 가치는 분명하다 — 소유권 시스템이 컴파일 타임에 데이터 레이스를 방지할 수 있으며, 이것은 고도로 병렬적인 GPU 프로그래밍 모델에서 자연스러운 강점이다. Zig의 가치 제안은 다르다: 암묵적 할당이 없고, 컴파일타임 계산이 동일한 언어이며, 제어 흐름에 대한 명시적 관리. GPU에서 암묵적 할당이 없다는 것은 존재하지 않는 힙 할당자에 대한 호출을 우연히 트리거하지 않는다는 뜻이다. comptime은 워크그룹 분배 전략, 메모리 레이아웃, 언롤링 팩터를 GPU 기능 세트에 기반하여 컴파일 타임에 동적으로 결정할 수 있다는 뜻이다 — 매크로도, 코드 생성 스크립트도 필요 없다.
GPU 프로그래밍에 어느 쪽이 더 적합한가? 필자는 답을 제시할 입장이 아니다. 두 언어의 GPU 시도는 모두 아직 너무 초기 단계이며, 데이터의 빈곤은 어떤 비교적 결론도 지지하지 않는다. 그러나 생태계 다양성의 관점에서, 서로 다른 철학을 가진 두 시스템 언어가 동시에 SPIR-V를 공략하고 있다는 것은 하나만 있는 것보다 낫다.
겸손한 선언
본 기사의 분석은 2026년 6월 26일 Zig 개발 로그, Ali Cheraghi의 “Zig and GPUs” 블로그 글, Lobsters 커뮤니티 논의, 그리고 Khronos SPIR-V 규격 공개 문서에 기초한다. 필자는 Zig 컴파일러 기여에 참여하지 않았으며, spirv64-vulkan 타겟에서 직접 셰이더를 빌드하고 실행해 본 적이 없다. 본문에 인용된 49% 동작 테스트 통과율 등 데이터는 개발 로그 저자의 자체 기술에서 가져온 것이며 독립적으로 검증되지 않았다. Rust GPU, Circle, Julia GPU 현황에 대한 기술은 공개 저장소, 커뮤니티 논의, 학술 논문에 기초한다 — 각 프로젝트의 실제 사용 가능성은 사용 시나리오에 따라 유의미하게 다를 수 있다. 이상의 어떤 영역에서든 직접적인 엔지니어링 경험이 있다면, 본 기사의 한계를 지적해 주기 바란다.