GN⁺: Go로 확장가능한 WASM 어플리케이션 작성 가능
(go.dev)- Go 1.24에서 WebAssembly(Wasm) 관련 기능이 확장됨
-
go:wasmexport
디렉티브가 추가되어 Wasm 모듈 외부에서 Go 함수를 호출할 수 있게 됨 - WASI를 위한 “reactor” 빌드 모드도 지원되어, 장기간 활성 상태로 코드를 실행할 수 있게 됨
- 이를 통해 Wasm 환경에서 Go 애플리케이션을 더 유연하게 확장할 수 있는 가능성이 열림
WebAssembly and the WebAssembly System Interface
- WebAssembly는 웹 브라우저에서 고성능의 저수준 코드를 실행하기 위해 만들어진 이진 포맷임
- 현재는 브라우저 외부에서도 광범위하게 활용되고, WebAssembly System Interface(WASI)를 통해 시스템 리소스와 상호작용 가능함
- Go는 1.11 버전에서 js/wasm 포트를 통해 Wasm 컴파일을 지원하기 시작했으며, 1.21 버전에서는 새로운 GOOS=wasip1 포트를 통해 WASI 프리뷰 1 시스템 호출 API를 타겟으로 하는 새로운 포트를 추가
go:wasmexport를 사용한 Go 함수의 Wasm Export
- Go 1.24에서 새로 추가된
go:wasmexport
디렉티브를 통해, Go 함수를 Wasm 모듈 외부에서 호출할 수 있도록 export로 노출 가능함 - 예:
//go:wasmexport add
처럼 선언한 뒤 함수를 작성하면, Wasm 호스트가 해당 함수를 호출할 수 있음 - 이는 cgo의
export
디렉티브와 유사하지만, 더 간단한 메커니즘으로 구현됨
Building a WASI Reactor
- WASI “reactor”는 지속적으로 작동하며 이벤트나 요청에 반응할 수 있는 WebAssembly 모듈을 의미함
- Go 1.24에서는
-buildmode=c-shared
옵션을 사용해 WASI reactor 빌드를 지원함 - 이 빌드 플래그는 링커에게 _start 함수(명령 모듈의 진입점)를 생성하지 말고, 대신 _initialize 함수를 생성하도록 지시함
- reactor는
_initialize
함수를 통해 초기화가 진행되고, main 함수 대신 이 함수를 먼저 호출해야 함
- reactor는
- Wazero 같은 런타임과 함께 사용하면,
_initialize
호출 후에 export된 함수를 원하는 만큼 재호출할 수 있음 - 이 방식은 애플리케이션의 플러그인 혹은 확장 기제로 Wasm을 활용하는 환경에서 유용함
호스트와 클라이언트 간의 풍부한 타입 지원
- Go 1.24에서는
go:wasmimport
로 호출되는 함수의 매개변수/반환형에 대해 제약이 완화됨 - 예를 들어, bool, string, int32 포인터, 구조체 포인터 등을 전달할 수 있음
- 다만 64비트와 32비트 환경 차이 등으로 여전히 제한이 존재함
- 이는 Go Wasm 애플리케이션을 보다 자연스럽고 편리하게 작성할 수 있도록 하며, 불필요한 타입 변환을 제거
제한 사항
- Wasm은 병렬 처리가 없는 단일 스레드 아키텍처
-
go:wasmexport
함수는 새로운 고루틴을 생성할 수 있지만, 백그라운드 고루틴을 생성하는 함수는 go:wasmexport 함수가 반환된 후 Go 기반 Wasm 모듈로 다시 호출될 때까지 실행을 계속하지 않음 - 일부 타입 제한이 완화되었지만, 여전히 go:wasmimport 및 go:wasmexport 함수와 함께 사용할 수 있는 타입에는 제한이 있음
- 포인터를 포함한 복합 타입 전달에는 아직 제약 사항이 존재
결론
- Go 1.24의 WASI reactor 빌드 및
go:wasmexport
기능 추가는 Go의 Wasm 생태계를 크게 확장시키는 개선사항임 - 이를 통해 개발자가 더 다양한 Go 기반 Wasm 애플리케이션을 만들 수 있도록 하여 Wasm 생태계에서 Go의 새로운 가능성을 열어줌
Hacker News 의견
-
Go로 생성된 WASM 바이너리가 매우 큰 문제점이 있음. TinyGo는 이를 극복하지만 컴파일 속도가 느리고 라이브러리 선택에 주의가 필요함. 둘 다 극복하려면 많은 인내심이 필요함
- Cloudflare workers에서 Go WASM을 시도하려면 바이너리 크기 때문에 구독이 필요함
- 마지막 시도에서는 hello-world는 실행 가능했지만, 더 복잡한 것은 크기 제한을 초과했음
- 안타까운 상황임
-
놀라운 점임. 기억해야 할 사항:
- Go의 웹어셈블리 작업은 Go 팀이 아닌 자원봉사자들에 의해 설계되고 구현되었음. 따라서 일정은 자원봉사자의 가용성에 따라 달라짐
-
Go 1.24 이전에도 Go 함수를 JS로 내보내는 것이 가능하지 않았는지 기억이 잘 나지 않음. 이전에 JS에서 내보낸 Go 함수를 문제없이 호출할 수 있었던 것으로 기억함
- 새로운 WASI 기능이 이전에 비해 어떻게 개선되었는지 설명해주면 도움이 될 것임 (FFI를 통해 더 많은 유형을 지원하는 것 외에)
- 두 번째 질문: 포인터를 정수로 캐스팅하여 문자열 및 복잡한 유형을 WASM 모듈의 인스턴스 메모리에서 추출할 수 있었음. Go에서 내 유형의 이진 표현이 안정적임이 보장된다면, goos=wasip1을 사용할 때 생성된 WASM 모듈에 포인터를 전달하는 이 방법이 여전히 유효한지 궁금함
-
메인 패키지에서 대문자로 시작하는 모든 함수를 내보내는 것이 더 "Go"스러웠을 것 같음. 내보내기는 언어에서 일반적으로 작동하므로 소문자로 시작하는 것을 명시적으로 이름 지정할 때만 컴파일러 지시문을 사용하는 것이 좋음
- 이는 기존 cgo 내보내기 방식과 동일함. 이전의 예시를 따르는 것임. 사용성은 여전히 언어 외부에 있음
-
WASM 컴포넌트 모델과의 작업에 대한 언급이 없음
-
Go와 WASM의 가비지 컬렉션은 어떻게 작동하는지 궁금함
-
강력한 타입과 뛰어난 WASM 지원을 갖춘 저수준 언어가 있었으면 좋겠음
-
호스트 프로그램에서 실행 중인 WASM 모듈을 어떻게 디버그하는지 궁금함
-
더 많은 WASM 기능에 대한 열망이 젊은 생태계를 돌이킬 수 없게 해칠까 걱정됨. Go가 WASM에 추가한 대부분의 기능은 컴포넌트 모델 제안이 이미 병합되었다면 네이티브로 수행할 수 있었음
- 표준은 천천히 발전하고 있으며 채택이 증가함에 따라 WASI와 같은 비표준 기능을 영원히 지원해야 할 위험이 있음