효율적으로 WebAssembly에 GC 언어를 올리는 새로운 방법
(v8.dev)- Java, Kotlin, Dart, Python, C# 같은 GC 언어를 WebAssembly로 옮길 때, WasmGC는 기존 VM 재컴파일이 아니라 Wasm 자체의 GC 구조체·배열·타입 시스템을 활용함
- 전통적 WasmMVP 포팅은 기존 VM과 최적화를 재사용하기 쉽지만, 선형 메모리 안에 GC나
malloc/free를 함께 싣는 비용과 스택 참조·순환 참조·단편화 제약을 가짐 - WasmGC는 VM이 객체와 메모리를 직접 관리해 바이너리 크기를 줄일 수 있으며,
fannkuch벤치마크에서 2.3K로 C/Rust의 6.1~9.6K보다 작았음 - WasmGC는 더 높은 수준의 중간 표현이라 Binaryen
wasm-opt와 V8 런타임 최적화가 중요하며, Java 벤치마크 평균 1.9×, Google Sheets Calc Engine 약 30% 개선 사례가 있음 - 표준화와 브라우저 지원은 진행됐지만, 기존 VM을 그대로 컴파일하는 방식이 아니므로 언어 구조를 WasmGC 원시 요소로 낮추는 새 툴체인 작업이 필요함
GC 언어를 Wasm으로 옮기는 두 가지 방식
- GC 언어의 WebAssembly 포팅은 크게 두 갈래로 나뉨
- 전통적 포팅: 기존 언어 VM을 2017년에 출시된 WebAssembly Minimum Viable Product인 WasmMVP로 컴파일함
- WasmGC 포팅: GC 제안에 정의된 Wasm의 GC 구조로 언어를 컴파일함
- 전통적 포팅은 새 CPU 아키텍처로 언어를 옮길 때처럼 VM의 파서, 라이브러리 지원, GC, 최적화기를 공유하고 새 백엔드만 추가하는 모델에 가까움
- WasmGC 포팅은 언어를 새 아키텍처가 아니라 새 VM으로 옮기는 방식에 가까움
- Java를 JavaScript로 컴파일하는 J2CL처럼, 언어 객체를 대상 VM의 객체로 표현하고 대상 VM의 GC가 관리함
- WasmGC는 JavaScript VM, JVM, CLR보다 낮은 수준을 지향하지만, VM 관리 구조체와 배열, 타입 관계를 제공한다는 점에서 WasmMVP보다 높은 수준임
전통적 WasmMVP 포팅의 장점과 한계
- 기존 VM 코드와 언어 구현, 최적화를 거의 그대로 재사용할 수 있다는 점이 전통적 포팅의 가장 큰 장점임
- WasmMVP 결과물은 선형 메모리, 테이블, 함수 등 WasmMVP의 기본 구조를 사용함
- 브라우저, Node.js, workerd, Deno, Bun처럼 이미 GC가 있는 VM 안에서 Wasm을 실행하는 경우가 많음
- 이 환경에서 GC 구현을 Wasm 바이너리에 포함하면 불필요한 크기 증가가 생김
- C, C++, Rust처럼 선형 메모리를 쓰는 언어도 의미 있는 할당을 하면
malloc/free코드를 함께 포함해야 함 dlmalloc은 6K, 크기를 우선한emmalloc도 1K를 넘음
- WasmGC는 VM이 메모리를 자동 관리하므로 Wasm 안에 GC나
malloc/free를 넣을 필요가 없음- 기존 WasmGC 글의
fannkuch벤치마크에서 WasmGC는 2.3K, C/Rust는 6.1~9.6K였음
- 기존 WasmGC 글의
순환 수집, 스택 참조, 단편화
- 브라우저에서 Wasm은 JavaScript와 Web API와 자주 상호작용하지만, WasmMVP와 reference types만으로는 Wasm과 JS 사이의 양방향 링크에서 세밀한 순환 수집이 어려움
- JS 객체 링크는 Wasm 테이블에만 둘 수 있음
- JS에서 Wasm으로 돌아가는 링크는 전체 Wasm 인스턴스를 하나의 큰 객체처럼 참조할 수 있음
- WasmGC는 VM이 인식하는 Wasm 객체를 정의하므로 Wasm과 JavaScript 사이에 적절한 참조를 만들 수 있음
- GC 언어는 호출 스코프의 지역 변수처럼 스택 위 참조도 인식해야 함
- 전통적 포팅에서는 Wasm 샌드박싱 때문에 프로그램이 자기 스택을 검사할 수 없음
- 대안으로 shadow stack을 쓰거나 JavaScript 이벤트 루프의 turn 사이처럼 스택에 참조가 없을 때만 GC를 수행할 수 있음
- 향후 Wasm의 stack scanning 지원은 전통적 포팅에 도움이 될 수 있음
- 현재 스택 참조를 오버헤드 없이 자동 처리하는 방식은 WasmGC임
- WasmMVP 선형 메모리의
malloc/free는 장기 실행 프로그램에서 메모리 단편화를 만들 수 있음- 전체 미사용 메모리가 충분해도 연속된 큰 블록이 없으면 큰 할당에 실패할 수 있음
- 단편화는 Wasm 모듈이 메모리를 더 자주 키우게 만들고, 오버헤드와 out-of-memory 오류로 이어질 수 있음
- WasmGC는 VM이 GC 힙을 압축하기 위해 객체를 이동할 수 있어 단편화를 피할 수 있음
개발자 도구와 언어 의미론
- 전통적 WasmMVP 포팅에서는 객체가 선형 메모리의 바이트로 놓이기 때문에 개발자 도구가 높은 수준의 타입 정보를 보기 어려움
- WasmGC에서는 VM이 GC 객체를 관리하므로 도구 통합이 더 쉬워짐
- Chrome DevTools의 Memory 탭에서 WasmGC 프로그램의 heap snapshot을 볼 수 있음
- 연결 리스트 예제에서는
$Node타입 이름과 다음 객체를 참조하는$next필드가 표시됨 - 객체 수, shallow size, retained size 같은 일반 heap snapshot 정보도 제공됨
- Chrome DevTools의 debugger도 WasmGC 객체에서 동작함
- 전통적 포팅은 기존 VM을 다시 컴파일하므로 기대한 언어 의미론을 그대로 얻기 쉬움
- WasmGC 포팅은 새 GC 타입인 구조체와 배열로 언어 구조를 표현해야 하므로 효율성을 위해 의미론 타협이 필요할 수 있음
- WasmGC 구조체 필드는 고정 인덱스와 타입을 가지므로, 필드를 더 동적으로 접근하려는 언어는 어려움을 겪을 수 있음
- WasmGC에는 현재 interior pointer가 없으며, 이런 제한은 시간이 지나며 개선될 것으로 기대됨
- 대상 VM으로 컴파일하는 다른 사례에서도 의미론 선택이 존재함
- dart2js의 숫자 동작은 Dart VM과 다름
- IronPython의 문자열은 C# 문자열처럼 동작함
- 반면 dart2wasm은 Wasm이 Dart에 필요한 숫자 타입을 효율적으로 표현할 수 있어 숫자 의미론 타협 없이 동작함
WasmGC 포팅에 필요한 툴체인 작업
- WasmGC 포팅은 기존 VM을 단순히 재컴파일하는 방식이 아님
- 파서 로직이나 AOT 최적화처럼 런타임 GC와 직접 통합되지 않는 일부 코드는 재사용할 수 있음
- 일반적으로 언어 구조를 WasmGC 구조체와 배열로 낮추는 새 코드가 많이 필요함
- Lua VM처럼 C로 작성된 VM은 몇 분 만에 Wasm으로 컴파일할 수 있지만, Lua의 WasmGC 포팅은 Lua 구조를 WasmGC 타입 시스템 제약 안에서 어떻게 표현할지 결정하고 구현해야 함
- WasmGC 포팅의 큰 단점은 툴체인 노력임
- 이상적인 상태는 WasmGC 타입 시스템이 모든 언어를 효율적으로 지원하고, 각 언어가 WasmGC 포트를 구현하는 것임
- WasmGC 타입 시스템의 향후 추가 기능이 첫 번째 부분을 도울 수 있음
- 공통 툴체인 작업을 공유하면 두 번째 부분의 부담을 줄일 수 있음
WasmGC가 더 잘 최적화될 수 있는 이유
- WasmGC는 WasmMVP보다 높은 수준의 중간 표현이라 최적화 여지가 큼
- 예시 함수에서 GC 객체를 두 번 할당하고 필드에
10을 저장한 뒤 반환하는 코드는 논리적으로return 10으로 줄일 수 있음- WasmMVP에서는 할당이
malloc호출로 바뀌고,malloc은 선형 메모리에 부작용이 있는 복잡한 함수라 최적화기가 두 번째 할당이 첫 번째 객체 필드를 바꾸지 않는다고 확신하기 어려움 - WasmGC에서는 할당이
struct.new명령으로 표현되고, VM 작업으로 추론할 수 있어 참조와 필드 값을 추적할 수 있음
- WasmMVP에서는 할당이
- WasmGC는 명시적 함수 포인터
ref.func, 이를 통한 호출call_ref, 구조체·배열 필드 타입도 제공함 - WasmMVP가 네이티브에 가까운 속도를 낼 수 있는 이유는 보통 LLVM 같은 강력한 최적화 컴파일러가 Wasm 생성 전에 대부분의 최적화를 수행하기 때문임
- LLVM은 WasmGC를 지원하지 않으며, 많은 GC 언어도 LLVM을 쓰지 않기 때문에 WasmGC에는 다른 최적화 모델이 필요함
Binaryen과 wasm-opt 최적화
- WasmGC는 일반 목적 최적화를 Wasm으로 낮춘 뒤 수행할 수 있어, 여러 언어 툴체인이 공통 Wasm-to-Wasm 최적화기를 공유할 수 있음
- V8 팀은 WebAssembly 툴체인 최적화 프로젝트인 Binaryen의 WasmGC 지원에 투자함
- 각 툴체인은
wasm-opt명령줄 도구로 Binaryen을 사용할 수 있음
- 각 툴체인은
- Binaryen은 기존 WasmMVP용 인라이닝, 상수 전파, 죽은 코드 제거 같은 최적화를 이미 갖고 있으며, 이들 대부분은 WasmGC에도 적용됨
- WasmGC를 위해 추가된 주요 최적화는 다음과 같음
- Escape analysis: 힙 할당을 로컬로 이동
- Devirtualization: 간접 호출을 직접 호출로 변환
- 더 강력한 전역 죽은 코드 제거
- Whole-program type-aware content flow analysis, GUFA
- Cast optimizations: 중복 캐스트 제거와 캐스트 위치 이동
- Type pruning
- Type merging
- 로컬, 전역, 필드, 시그니처에 대한 타입 정제
- Binaryen의 새 GC 최적화와 사용법은 Binaryen docs에 정리되어 있음
- J2Wasm 출력에서 Java 성능을 측정했을 때,
wasm-opt는 각 벤치마크에서 속도를 높였고 평균 1.9× 빨라짐
V8에서의 WasmGC 최적화
- GC 언어는 C, C++, Rust처럼 컴파일 시점 인라이닝에 크게 의존하는 언어와 성능 모델이 다름
- Java, Dart 같은 GC 언어는 보통 VM이 런타임에 인라이닝과 최적화를 수행함
- Java에서는 모든 호출이 처음에는 간접 호출로 시작함
- 자식 클래스는 부모 함수를 오버라이드할 수 있고, 부모 타입 참조로 자식을 호출해도 마찬가지임
- 툴체인이 간접 호출을 직접 호출로 바꾸면 이득이 있지만, 실제 Java 코드에는 정적으로 직접 호출이라고 추론하기 어려운 경로가 많음
- V8은 WasmGC를 위해 speculative inlining을 구현함
- 런타임에 간접 호출을 관찰함
- 특정 호출 지점이 적은 호출 대상처럼 단순한 동작을 보이면 가드 체크와 함께 인라이닝함
- Google Sheets Calc Engine은 스프레드시트 수식을 계산하는 Java 코드베이스이며, 기존에는 J2CL로 JavaScript에 컴파일되어 왔음
- V8 팀은 Sheets와 J2CL과 협력해 이 코드를 WasmGC로 포팅함
- 이 코드에서 speculative inlining은 V8이 WasmGC에 구현한 단일 최적화 중 가장 큰 효과를 보였음
- 약 30% 속도 향상으로, 측정 가능한 다른 최적화 전체보다 큰 개선을 보임
- V8의 기타 WasmGC 최적화에는 load elimination, type-based optimizations, branch elimination, constant folding, escape analysis, common subexpression elimination 등이 포함됨
- WasmGC의 타입 정보는 런타임 최적화에도 사용됨
ref.test가 특정 타입 검사를 통과하면 같은 타입으로의ref.cast는 성공해야 함- Java의
instanceof뒤 다운캐스트 같은 패턴에서 캐스트를 제거할 수 있음
- WasmMVP에서는 툴체인과 VM 최적화의 역할 분리가 비교적 명확했지만, WasmGC에서는 GC 언어 특성과 WasmGC 표현의 최적화 가능성 때문에 툴체인과 VM 최적화가 더 겹칠 수 있음
현재 상태와 시작점
- WasmGC는 W3C에서 phase 4에 도달해 완전하고 최종화된 표준이 됨
- Chrome 119는 WasmGC 지원을 포함함
- Firefox 120은 같은 달 말 WasmGC 지원과 함께 출시될 것으로 예상됨
- Flutter demo에서는 WasmGC로 컴파일된 Dart가 위젯, 레이아웃, 애니메이션을 포함한 애플리케이션 로직을 구동함
- WasmGC를 지원하는 툴체인은 다음과 같음
- 개발자 도구 섹션의 작은 예제 소스 코드는 손으로 작성한 “hello world” WasmGC 프로그램 예시임
$Node타입 정의와struct.new를 사용한 생성 방식을 볼 수 있음
- Binaryen wiki는 컴파일러가 최적화하기 좋은 WasmGC 코드를 내보내는 방법을 다룸
- GC Implementation - Lowering Tips
- Java, Dart, Kotlin 툴체인이 사용하는 Binaryen 패스와 플래그도 참고할 수 있음
댓글과 토론
Hacker News 의견들
-
WASM은 얇은 허리(thin waist) 의 예로 보이고, 가비지 컬렉터까지 더해지면 N×M이 아니라 N+M 구조가 됨. 즉 N개 언어 + M개 가상 머신 + G개 가비지 컬렉터로 갈 수 있고, V8에는 이미 성숙한 가비지 컬렉터가 있음
WASM에서 JVM으로 가는 도구가 있는지 궁금했는데 GitHub에 하나가 있었음. 직접 써본 건 아니고, JVM도 성숙하고 병렬적인 가비지 컬렉터를 갖고 있어서 찾아본 것임
WASMGC가 이렇게 빨리 나올 줄 몰랐기 때문에, 이제는 입출력 병렬성뿐 아니라 진짜 병렬성을 위한 WASM Threads도 기대됨
비동기, 병렬성, 가비지 컬렉션을 효과적으로 풀 기회가 생기면 WASM은 더 강해지고 개발자에게 혼란이나 어려움의 원천이 되지 않을 수 있음. WASI가 중요한 이유도 POSIX만큼 안정적인 API를 정의할 기회이기 때문이라고 봄
1: https://www.oilshell.org/blog/2022/02/diagrams.html
2: https://github.com/cretz/asmble- https://github.com/cretz/asmble는 잘 동작하지만, 대부분 재미로 만든 프로젝트였고 지금은 사실상 유지보수하지 않음
WASM 중간 표현을 JVM 바이트코드로 어떻게 매핑했는지에 대한 아이디어와 설명이, 더 공식적인 구현을 만드는 사람에게 도움이 되길 바람. 현재 WASM GC 지원 계획은 없음 - GraalVM은 WASM 실행 파일을 실행할 수 있음: https://www.graalvm.org/latest/reference-manual/wasm/
- 22년 늦게 CLR의 발자취를 따라가는 셈임
“20개가 넘는 프로그래밍 도구 업체가 C++, Perl, Python, Java, COBOL, RPG, Haskell을 포함한 약 26개 프로그래밍 언어를 .NET에서 제공한다”
출처: https://news.microsoft.com/2001/10/22/massive-industry-and-d... - 조금 꼼꼼히 따지자면 JVM은 가비지 컬렉터들보다 더 큰 개념임[^1]. 다만 JVM이 가비지 컬렉션 실험장이 되어온 건 맞음
학계가 JVM에 유독 과감해서라기보다, JVM 프로그램이 널리 쓰였고 단일 가비지 컬렉션 기법을 압박하는 방식으로 자주 사용됐기 때문임
[^1]: https://www.baeldung.com/jvm-garbage-collectors. Azul JVM도 별도의 다른 가비지 컬렉터를 갖고 있음. 이건 내가 아는 것들뿐이고, 빠뜨린 것도 꽤 있을 것임 - 엄밀히 말하면 스레드는 동시성을 제공하지, 반드시 병렬성을 제공하는 건 아님. 그래서 진짜 병렬성이라고 부르는 건 이상하게 들림
코루틴과 비교해서 말한 것 같지만, 코루틴은 순차적임. 실행 순서는 임의적일 수 있어도 동시에 실행되지 않는다고 의존할 수 있음
wasm이 스레드를 채택한다면 또 하나의 안타까운 “Worse is Better” 상황이 될 것 같음. 스레드는 우리가 고안한 동시성 모델 중 최악에 가까움. 물론 concurrent COMEFROM 같은 것을 빼면
- https://github.com/cretz/asmble는 잘 동작하지만, 대부분 재미로 만든 프로젝트였고 지금은 사실상 유지보수하지 않음
-
Julia WASM 도구는 이 기능을 지원하거나 필요로 하는 앱을 만들 수 있음. 예를 들어 ODE 솔버를 WASM으로 컴파일하는 예제가 https://tshort.github.io/WebAssemblyCompiler.jl/stable/examp...에 있음
바로 동작하려면 Chrome v119가 필요한데, 이 버전이 가비지 컬렉션 지원을 처음 활성화했기 때문임. WASM 컴파일러 첫 페이지 https://tshort.github.io/WebAssemblyCompiler.jl/stable/에 더 자세한 내용이 있음- 정말 큰 진전임
새Memory{T}타입 덕분에 더 많은 코드를 컴파일할 수 있게 되면 좋겠음
- 정말 큰 진전임
-
“wasm이 새 LLVM” 같은 느낌이 듦. 비슷하게 느끼는 사람이 있는지 궁금함
무엇을 하려는지는 이해하고 그 힘을 보여주는 시연도 봤지만, 대부분은 여전히 기술적으로 매우 저수준이고 실제로 쓰려면 꽤 번거로워지는 경우가 많음
현재 방식 대신 대상으로 선택할 만큼 더 넓게 채택될 준비가 언제쯤 된다고 보는지 궁금함- LLVM은 가상 머신이 아니지만 WASM은 가상 머신임
이름 때문에 흔한 오해가 생기는데, LLVM은 초기에 가상 머신을 의도했지만 실제로 그런 적이 없고 지금도 아님. 홈페이지 첫 문장에서도 이를 분명히 함: https://llvm.org/
기본적으로 시간이 지나며 바뀌는 중간 표현을 구현한 C++ 라이브러리 묶음이고, 컴파일러 작성을 돕는 도구임
10년쯤 전 Google에 WASM과 비슷한 영역을 겨냥한 프로젝트가 있었는데, 이 오해를 했던 것 같음. LLVM을 가상 머신으로 생각했던 것임. 아마 PNaCl 같은 것이었음
LuaJIT이 Lua 5.1에서 Lua를 고정한 것과 조금 비슷함. Lua는 표준이 아니었지만, 재구현을 위해 특정 버전을 고정할 수는 있음. 다만 그 접근에는 명백한 문제가 있는데, 재구현하는 쪽이 같이 시간 속에 고정하는 버그들을 모두 알지 못한다는 점임
WASM의 “타협”에는 눈썹이 올라간 적도 있지만, 의심할 수 없는 한 가지는 WASM이 실제로 가상 머신이라는 점임
WASM GC 발표를 봤는데, 만든 사람들이 타협점을 솔직하게 말했음. 예를 들어 초기에는 런타임 캐스트가 필요하고, 측정된 오버헤드는 합리적인 수준이라는 식임. 이런 태도가 오히려 신뢰를 줌
https://old.reddit.com/r/ProgrammingLanguages/comments/17crk... - 비슷한 질문을 하려고 왔음
솔직히 wasm을 잘 모르겠음. 우리가 정확히 어떤 문제를 풀려는지 명확하지 않음
목표가 JavaScript나 JavaScript로 변환된 코드가 아닌 언어로 애플리케이션을 작성하는 것인지 궁금함. 그게 브라우저 DOM 표시 계층과 상호작용하는 부분까지 포함하는지 모르겠음. 내 이해로는 실제로는 그걸 할 수 없는 것 같은데, 맞다면 목표는 Flash처럼 DOM 안의 전용 캔버스에서 애플리케이션을 실행하는 것인지도 궁금함. 그렇다면 그 틈새가 얼마나 큰지 모르겠음
다른 스레드에서는 Cloudflare Workers에 wasm을 쓴다는 얘기도 있었음. 아마 그게 JavaScript 인터프리터를 통해 동작하기 때문일 것임. 하지만 사람들이 임의의 런타임으로 Cloudflare Workers를 작성하고 싶다면, Cloudflare가 그냥 다른 런타임 지원을 추가하면 되는 것 아닌가 싶음. 그 정도 역량은 있을 것임
또는 JavaScript 생태계와의 연결 외에, wasm이 바이트코드로서 고유하게 훌륭한 점이 있는지 궁금함
이 기술이 왜 흥미로운지 설명하는 입문 자료를 놓친 느낌인데, 이 시점에 묻기에는 조금 민망함 - wasmtime 같은 것을 보면, 기본적으로 여러 언어를 지원하는 매우 안전한 플러그인을 가진 네이티브 프로그램을 만들 수 있음. 같은 방식으로 브라우저에서도 기본 보안 모델을 유지하며 동작함
LLVM은 상당한 코딩 없이는 “기본적으로 쉬운 보안 샌드박스”를 제공하지 않음. wasmer에는 LLVM 백엔드가 있긴 하지만, 개인적으로는 이 점이 WASM의 가장 큰 차별점임 - “WASM이 새 LLVM”이라기보다는 WASM이 새 JVM에 가까움
- “wasm은 새 Emscripten”에 더 가까움. 모든 브라우저에 걸친 훨씬 더 공식화된 표준임
LLVM과 같은 방향으로 가긴 하지만, 아직 거기와는 거리가 큼
- LLVM은 가상 머신이 아니지만 WASM은 가상 머신임
-
아무리 대단하고 많은 가능성을 열어준다고 해도, 오늘날 브라우저가 극도로 복잡하고 직접 만드는 진입 장벽이 거의 너무 가파르다는 사실을 계속 생각하게 됨
- 직접 브라우저를 만드는 곡선은 너무 가파른 수준이 아니라 말 그대로 불가능함. 운영체제 전체를 만드는 편이 브라우저보다 훨씬 쉬움
- Google과 Apple조차 처음부터 시작하지 않았고, WebKit, 즉 KDE KHTML을 사용했음
- 직접 Linux를 만드는 것도 불가능하지만, 그걸 두고 다들 불평하지는 않음. Android가 쉽게 직접 만들 수 없다고 해서 비난하지도 않음
세계에서 가장 성공적이고 접근 가능한 하이퍼미디어 플랫폼이 왜 쉽게 구현 가능해야 하는지 모르겠음. 당연히 세계에서 가장 성공한 온라인 시스템은 복잡하고 기능이 많음. 그래서 우리가 쓰고, 그래서 승리한 것임 - 맞는 말이지만, 이 작업은 전체 브라우저 엔진 없이 실행할 수 있는 더 단순한 대안 런타임의 기반이 됨. V8에 의존하는 Node나 Deno를 생각하면 됨
- 현대의 많은 것들이 이미 이 지점에 도달했음. 직접 자동차를 만들 수 없고, 집도 완전히 처음부터 직접 짓기는 사실상 불가능함. 건축법 같은 전문 지식이 너무 많이 필요함
-
이게 결국 만들어지고 출시된 게 인상적임. WASM에 GC를 넣겠다는 계획은 몇 년 동안 들어왔고, 실제로 일어날지 확신이 없었음
WASM을 대상으로 하는 언어들 중 런타임을 포함하느라 바이너리가 커지는 문제가 있는 경우에 얼마나 도움이 될지 궁금함. 기억하기로 Blazor는 hello world만 해도 1MB 정도가 필요했는데, WasmGC가 여기에 도움이 될지 모르겠음- WasmGC가 .NET에서 쓸 만해지기까지는 시간이 걸릴 수 있음. 논의에 따르면 첫 버전의 WasmGC는 몇몇 .NET 특화 시나리오를 다루는 좋은 방법이 없고, 그 시나리오들은 “post-post-mvp”로 분류되어 있음 [0]
물론 걱정되는 건, .NET만 필요로 하는 기능이라면 추가할 유인이 크지 않다는 점임. 그 시점에서는 특정 GC 버전을 캐시해두고 다른 WASM 어셈블리가 로드할 수 있는 형태의include같은 기능이, 고통을 만들더라도 더 유용할 수 있음
[0] - https://github.com/WebAssembly/gc/issues/77 - Chrome의 Wasm 팀은 작업 중 자료구조를 할당하는 Fannkuch 벤치마크를 C, Rust, Java에서 컴파일했음. C와 Rust 바이너리는 컴파일러 플래그에 따라 6.1K에서 9.6K였고, Java 버전은 2.3K로 훨씬 작았음
C와 Rust는 가비지 컬렉터를 포함하지 않지만 메모리 관리를 위해 여전히 malloc/free를 묶어야 함. Java가 여기서 더 작은 이유는 메모리 관리 코드를 전혀 묶을 필요가 없기 때문임
https://developer.chrome.com/blog/wasmgc/
Blazor의 경우에는 GC에만 도움이 될 것임. 기억하기로 Blazor는 전체 dotnet 런타임을 함께 보내야 함
- WasmGC가 .NET에서 쓸 만해지기까지는 시간이 걸릴 수 있음. 논의에 따르면 첫 버전의 WasmGC는 몇몇 .NET 특화 시나리오를 다루는 좋은 방법이 없고, 그 시나리오들은 “post-post-mvp”로 분류되어 있음 [0]
-
Kotlin의 새 wasm 지원은 꽤 기대됨. 브라우저를 대상으로 할 수 있는 Compose Multiplatform 실험 버전이 있고, 이것이 WASM을 사용할 예정임
Compose Multiplatform은 기본적으로 Android용 Google Jetpack Compose에 다른 플랫폼 지원을 더한 것임
며칠 전 기준으로 iOS 지원은 알파이고 내년에 베타가 될 예정임. Android와 데스크톱 지원은 이제 안정화됨. 이들이 안정되면 사실상 어떤 플랫폼에서도 동작하는 UI 애플리케이션을 작성할 수 있음
wasm 컴파일러는 Kotlin의 다음 주요 릴리스인 Kotlin 2.0과 함께 출시될 예정이고, 여기에는 새 컴파일러 k2가 포함됨. 내년 초쯤 가능해 보임. k2는 현재 베타로 제공되며 Kotlin 1.9.x에서 활성화할 수 있음
Kotlin 멀티플랫폼 생태계의 장점은 이미 여러 플랫폼에서 동작하는 라이브러리가 많다는 점임. 따라서 wasm 컴파일러도 빠르게 그 일부가 되고 좋은 라이브러리들을 물려받을 것임. 대체로 빌드 설정을 해당 생태계를 대상으로 바꾸고, 빠진 플랫폼별 동작을 구현하면 됨
이 영역에서 또 흥미로운 점은 서로 다른 언어로 작성된 라이브러리를 사용하고 연결하는 것임. 예를 들어 많은 플랫폼별 부분은 이미 존재하는 C나 Rust 라이브러리에 의존하게 될 가능성이 큼. 많은 경우 Kotlin Native가 쓰는 것과 같은 라이브러리일 수도 있음- 웹에서 Compose Multiplatform이 어떻게 동작하는지 궁금함. Flutter처럼 Canvas에 그리는 방식인지 궁금함
데스크톱, 웹, iOS, Android에서 같은 위젯을 쓰는지 궁금함. Android에서는 네이티브라는 건 이해함. 다른 플랫폼에서는 다른 스타일을 흉내 내는지 모르겠음. 핵심 질문은 Android가 아닌 플랫폼에서 Compose Multiplatform 앱의 느낌이 어떤가임
예를 들어 웹의 Dart는 개인적으로 매우 버벅이는 느낌이고, 데스크톱의 Electron도 그렇음. 데스크톱의 Dart는 괜찮지만, 앱들이 대체로 버튼과 여백이 커서 실제 네이티브 앱이라기보다 에뮬레이터에서 도는 Android 앱처럼 느껴짐
새 프로젝트에 Kotlin을 언어로 추천할 만한지도 궁금함 - 시도할 때마다 Gradle 난장판 때문에 열정이 꺾였음
- 웹에서 Compose Multiplatform이 어떻게 동작하는지 궁금함. Flutter처럼 Canvas에 그리는 방식인지 궁금함
-
이 블로그 글과 Chrome 발표 글이 왜 Go를 언급하지 않는지 설명해줄 수 있는 사람이 있는지 궁금함
Go도 가비지 컬렉션을 쓰는데, 이 변화의 이점을 얻을 수 없다는 인상을 줌- WasmGC는 내부 포인터(interior pointer) 를 지원하지 않아서 Go는 이득을 보기 어려움. 적어도 fat pointer 같은 보기 흉한 방식 없이는 힘듦
-
런타임이 C/C++/Rust가 사용할 수 있는 내장 할당 라이브러리를 노출하는 것이 말이 될까 궁금함
프로그램은 자체 할당 라이브러리를 묶는 대신 그 라이브러리를 선택할 수 있을 것임- 공간을 조금 아끼는 실용적 관점에서는 말이 될 수 있음. 하지만 설계 관점에서는 이미 할 수 없는 일을 가능하게 하는 “최소 필수 기능”이 아니기 때문에, 표준 일부로 지정하면 이유 없이 부풀어 오를 뿐임
Wasm GC 원시 기능들은 가능한 한 작은 추가로 가장 큰 향상을 얻도록 설계됐음. SIMD 같은 다른 원시 기능이 추가될 때도 해당 기능을 달성하기 위한 “대략 최소 원시 기능”이기 때문임. 여기서는 가능한 한 작은 빌딩 블록이 필요함
하지만 malloc/free 같은 메모리 할당자 API는 최소 기능이 아님. 메모리 할당자는 실제로는 정책과 설계 결정의 묶음이고, 알고리즘은 거기서 따라 나옴. 그 영역의 기본 빌딩 블록은 “할당자가 소유한 선형 메모리 조각”이고, 그건 WASM이 첫날부터 갖고 있던 선형 메모리임
개별 런타임이 이를 제공하는 건 말이 될 수 있음. 예를 들어 내장 wasm import에 선형 메모리 일부를 넘기고 런타임 구현 할당자가 처리하게 하는 방식임. 하지만 표준화될 가능성은 거의 없다고 봄. 또한 GC 없는 Wasm 언어용 도구 대부분은 기존 코드베이스와 그 할당자를 포팅하는 방식에 맞춰져 있음. 결국 일회성 구현을 위해 유지할 코드가 늘어나는 셈이라, 들인 노력만큼 가치가 없을 수 있음 - 그 접근의 어려움은 할당 라이브러리의 동작이 표준화되고 결정적이어야 한다는 점임
결정적이지 않으면 프로그램 동작 차이가 중요해질 수 있음. 예를 들어 어떤 브라우저가 메모리를 더 잘 재사용하는 “똑똑한” 할당자를 넣으면, 다른 브라우저에서는 선형 메모리 단편화로 메모리 부족이 나는 프로그램이 그 브라우저에서는 성공할 수 있음. 브라우저들은 역사적으로 이런 차이를 피하려고 동작 표준화에 매우 애써왔음
malloc/free 구현의 동작을 표준화하는 것도 여러 이유로 까다로움. 첫째, free list를 어떻게 다루는지, 어떤 청크 크기를 쓰는지 등 복잡성이 큼. 사양에 넣기에는 많음. 둘째, 특정 동작을 표준화하면 개선할 수 없게 되는데, 더 나은 malloc/free 아이디어는 지금도 나오고 있음
이런 이유로 이것이 wasm 사양에 들어갈 가능성에는 회의적임
반면 GC는 이런 문제를 피함. 포인터 값이 관찰 가능하지 않기 때문에 사양화할 것이 없음 - 이게 영리한 방향처럼 들림.
alloc과 관련 함수를 런타임 함수로 추가해서 전통적인 코드가 호출하게 하고, 어떤 GC든 그 위에 쌓을 수 있게 하면 됨
- 공간을 조금 아끼는 실용적 관점에서는 말이 될 수 있음. 하지만 설계 관점에서는 이미 할 수 없는 일을 가능하게 하는 “최소 필수 기능”이 아니기 때문에, 표준 일부로 지정하면 이유 없이 부풀어 오를 뿐임
-
이 방향에는 조금 회의적임. WebAssembly의 복잡도를 크게 높이고 있음
가비지 컬렉터는 새는 추상화임. 어떤 것은 내부 포인터를 지원하고 어떤 것은 지원하지 않음. 어떤 것은 메모리를 공유하는 병렬 작업을 지원하고 어떤 것은 그렇지 않음. 어떤 것은 압축이 필요해서 C FFI가 더 어려워지고, 어떤 것은 그렇지 않음. 어떤 것은 그린 프로세스/스레드와 확장 가능한 스택에 쓰이는 메커니즘과 깊게 통합되어야 하고, 어떤 것은 그렇지 않음
Erlang, JavaScript, Python, Go 같은 언어를 보면 언어 수준의 선택이 어느 정도 가비지 컬렉터에 반영됨
여러 언어를 지원하는 범용/일반 가상 머신 아이디어는 JVM, CLR, Parrot 등으로 여러 번 시도됐고 성공은 제한적이었음. 이번에는 무엇이 다른지 궁금함- 이번에 다른 점은 WASM이 Sun JVM과 Microsoft CLR이 시도했던 방식과 시간 순서를 거꾸로 뒤집었다는 것임
JVM/CLR은 먼저 가상 머신, 중간 언어 명령어 사양, 런타임을 만들고, 그다음 최대한 널리 배포해서 보편적 클라이언트 채택을 기대했음. 즉 “중간 언어 VM”이 너무 매력적이라 업계 전체에 퍼질 것이라는 기대였음. 이 기대는 부분적으로만 맞았음. JVM/CLR은 데스크톱과 서버에는 퍼졌지만, 웹 브라우저에서는 Java Applet과 Microsoft Silverlight가 실패했고 모바일 플랫폼에서도 널리 채택되지 못했음
WASM은 반대 순서임. 이미 업계 전반에 배포되고 채택된 것, 즉 JavaScript에서 거꾸로 출발해 “가상 머신, 중간 언어 명령어 사양, 런타임”을 만듦
이 관점에서 JavaScript라는 이른바 “장난감 언어”는 모든 클라이언트에 먼저 널리 배포되기 위한 20년짜리 트로이 목마였음. 이제 업계가 “서버+데스크톱+브라우저+모바일에 보편 런타임인 JavaScript가 이미 있다는 걸 눈치챈 사람 있나? 중간 언어 런타임을 만들어 빠르게 하자”라고 하는 셈임
기술적 문제도 몇 가지 있었음. Sun JVM은 원시 포인터가 없어 C/C++ 같은 포인터 기반 언어의 성능 좋은 대상이 되기 어려웠고, MS CLR은 macOS에서 사용할 수 없었음. Silverlight의 최소 CLR은 예외임. 하지만 이런 기술적 한계보다 JavaScript가 해롭지 않은 트로이 목마처럼 배포된 시간 순서가 더 큰 설명력을 가짐 - JVM과 CLR은 여러 언어를 지원하도록 설계됐다고 보기 어려움. JVM에는 Java라는 주인 언어가 있고, JVM에서 실행하려는 다른 언어는 Java 타입 시스템에서 멀어질수록 더 큰 부담을 짊어짐
Wasm은 위의 바이트코드 형식 대부분보다 더 저수준임. 정적으로 타입이 붙은 구조체와 배열을 추가하는 Wasm GC, 타입 있는 함수를 가져오는 function-references 제안까지 포함해도 그렇음. Wasm GC에는 태그된 포인터i31ref에 대한 명시적 지원도 있음
이런 점들 때문에 Wasm GC는 더 저수준이라는 이유로, 위에 든 시도들보다 더 일반적임 - WASM-GC 지원은 의도적으로 가능한 한 단순하게 유지됨. https://github.com/WebAssembly/gc/blob/main/proposals/gc/MVP...를 보면 됨
여러 언어를 동등하고 매끄럽게 지원하는 “범용” 또는 “일반” VM을 만드는 것은 WASM-GC의 명시적 목표가 아님. 각 구현이 WASM이 제공하는 기본 지원 위에 자체 해킹과 특수 의미론을 필요로 할 수 있다고 예상하며, FFI와 언어 간 상호운용성은 완전히 별개 문제로 보기 때문에 괜찮다는 입장임 - 다른 점은 이 프로젝트의 중요도라고 봄
JavaScript 가비지 컬렉션은 계속 남을 것이고, 언급한 예시들 중 가장 끈질기게 자리 잡은 것으로 보임
많은 경우 더 열등하더라도 다른 언어와 애플리케이션/라이브러리는 거기에 맞춰야 함 - wasm이 하는 일은 이전 시도들과 다름. GC 기능은 다른 언어와의 상호운용성이나 언어 런타임 구현 간 개발 자원 공유를 위해 제공되는 게 아님
wasm은 관리 메모리 런타임 언어가 제한적인 메모리 모델의 한계 때문에 고통받지 않고 wasm 환경을 대상으로 삼을 수 있게 하려고 GC 기능을 제공하며, 부차적으로 번들 크기를 줄이려는 것임
wasm은 게스트 언어의 특성에 더 잘 맞도록 더 조정 가능한 GC 매개변수를 지원할 가능성이 있음. 또한 비교 대상인 일반 목적 언어 런타임과 달리, 언어 구현자에게는 아예 맞춤형 런타임을 만들 선택지가 없음
- 이번에 다른 점은 WASM이 Sun JVM과 Microsoft CLR이 시도했던 방식과 시간 순서를 거꾸로 뒤집었다는 것임
-
현재 WASM GC의 실현 가능성에 대한 여러 우려가 여기에서 다뤄짐. 영어 Google 번역본:
https://habr-com.translate.goog/ru/articles/757182/?_x_tr_sl...
원문:
https://habr.com/ru/articles/757182/
Java와 JVM 코드를 브라우저에서 효율적으로 실행하게 만드는 일을 10년 해온 TeaVM 작성자의 글임. https://teavm.org/
TeaVM의 기존 Java-to-JavaScript 변환은 브라우저의 JS GC를 사용하면서도 성능이 좋음. WASM GC가 더 성숙해서 그보다 더 빨라질지 지켜보면 흥미로울 것임- 흥미로운 글임
글에서 다룬 문제들에 대한 메모를 남기면, 수동 그림자 스택이 필요하다는 문제는 WasmGC에서 해결됨. 링크에서 말한 것처럼 JS에서 동작하는 방식과 같음
try-catch 부재는 이미 브라우저에 출시된 Wasm 예외 처리 제안으로 해결됨: https://github.com/WebAssembly/exception-handling/blob/main/...
null 검사도 대부분 WasmGC로 해결됨. 사양은 null 불가능 지역 타입을 정의하고, VM은 글에서 언급한 기법처럼 신호를 이용해 최적화할 수 있음. 예를 들어 Wizard가 그렇게 함
클래스 초기화는 글에서 말하듯 어려운 문제임. J2Wasm과 Binaryen은 도구 체인 수준의 정적 분석으로 이를 최적화하려고 작업 중임. 최근 내가 작성한 PR이 이 방향에서 진전을 냈음: https://github.com/WebAssembly/binaryen/pull/6061
글에서 언급한 vtable 오버헤드는 문제가 될 수 있음. 다만 좋은 측정값은 아직 모름. 메서드 디스패치를 위한 post-MVP 해결책 아이디어가 몇 가지 있지만, 아직 구체적인 것은 없음
null 검사와 trap에 대해서는, trap 대신 예외를 던지는 GC 명령 변형 논의가 있었음. 하지만 측정상 현재 큰 문제로 드러나지 않아 우선순위는 낮음
글쓴이가 스택 워킹, 신호, 메모리 제어가 중요한 영역이라고 한 것은 맞음
전반적으로 WasmGC와 예외 처리 덕분에, 오늘날 J2Wasm이 내보내는 Java에는 꽤 좋은 위치에 있음. 보통 Java를 JavaScript로 컴파일하는 J2CL보다 빠름. 그래도 개선 여지는 분명히 있음
- 흥미로운 글임