TinyGo, Python(componentize-py), C/C++(Emscripten), AssemblyScript, .NET Blazor 등 다양한 언어의 Wasm 지원 현황과 제약 사항, 언어 선택 가이드를 다룹니다.
WebAssembly의 핵심 약속 중 하나는 언어 독립성입니다. 하지만 현실에서 모든 언어의 Wasm 지원 수준이 동일한 것은 아닙니다. 가비지 컬렉터 유무, 런타임 크기, 도구 체인 성숙도에 따라 큰 차이가 존재합니다.
Go 1.21부터 GOOS=wasip1을 지원합니다. 하지만 표준 Go 컴파일러로 생성되는 Wasm 바이너리는 크기가 상당히 큽니다. Go 런타임(가비지 컬렉터, 고루틴 스케줄러 등)이 통째로 포함되기 때문입니다.
# 표준 Go 컴파일러 (WASI Preview 1)
GOOS=wasip1 GOARCH=wasm go build -o app.wasm main.go
# 결과: 약 5~15MB (런타임 포함)TinyGo는 마이크로컨트롤러와 WebAssembly를 타겟으로 하는 대안 Go 컴파일러입니다. LLVM 기반으로, 표준 Go 컴파일러 대비 훨씬 작은 바이너리를 생성합니다.
package main
import (
"fmt"
"net/http"
spinhttp "github.com/fermyon/spin-go-sdk/http"
)
func init() {
spinhttp.Handle(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
name := r.URL.Query().Get("name")
if name == "" {
name = "World"
}
fmt.Fprintf(w, `{"message":"Hello, %s!"}`, name)
})
}
func main() {}# TinyGo로 WASI Preview 2 빌드
tinygo build -target=wasip2 -o app.wasm main.go
# 결과: 약 200KB ~ 1MB (표준 Go 대비 1/10 이하)TinyGo는 표준 Go 라이브러리의 모든 패키지를 지원하지 않습니다. reflect, net/http(서버 측), database/sql 등 일부 패키지는 사용할 수 없거나 제한적으로만 동작합니다. 프로젝트 시작 전에 TinyGo의 패키지 호환성 문서를 확인해야 합니다.
| 항목 | 표준 Go | TinyGo |
|---|---|---|
| 바이너리 크기 | 5~15 MB | 200KB~1 MB |
| GC | 전체 Go GC | 보수적 GC (간소화) |
| 고루틴 | 완전 지원 | 제한적 지원 |
| 표준 라이브러리 | 거의 전부 | 부분적 |
| 컴파일러 백엔드 | Go SSA | LLVM |
| 컴포넌트 모델 | 미지원 | 지원 (wit-bindgen-go) |
Python은 GC 기반 인터프리터 언어이므로, Wasm으로 변환하는 데 근본적인 도전이 있습니다. CPython 인터프리터 전체를 Wasm으로 컴파일해야 하기 때문입니다.
componentize-py는 Python 코드를 Wasm 컴포넌트로 변환하는 도구입니다. CPython 인터프리터를 Wasm으로 임베딩하고, WIT 바인딩을 자동 생성합니다.
import json
from typing import Optional
# WIT에서 생성된 바인딩
from bindings.exports.example.api import handler
class Handler(handler.Handler):
def handle_request(self, method: str, path: str, body: Optional[bytes]) -> handler.Response:
if method == "GET" and path == "/api/status":
return handler.Response(
status=200,
headers=[("content-type", "application/json")],
body=json.dumps({
"status": "ok",
"language": "Python",
"runtime": "componentize-py"
}).encode()
)
return handler.Response(
status=404,
headers=[("content-type", "application/json")],
body=json.dumps({"error": "Not Found"}).encode()
)# componentize-py 설치
pip install componentize-py
# Wasm 컴포넌트로 빌드
componentize-py \
-d wit/world.wit \
-w api-handler \
componentize app \
-o app.wasm
# 결과: 약 10~20MB (CPython 인터프리터 포함)Python Wasm의 바이너리 크기는 10~20MB로 상당히 큽니다. CPython 인터프리터와 필수 표준 라이브러리가 포함되기 때문입니다. 엣지 환경에서는 이 크기가 부담이 될 수 있지만, 서버사이드에서는 콜드 스타트 시간이 여전히 기존 Python 런타임보다 빠릅니다.
성능이 중요한 부분만 Rust로 작성하고, Python에서 호출하는 하이브리드 접근도 가능합니다.
use pyo3::prelude::*;
#[pyfunction]
fn process_data(data: Vec<f64>) -> PyResult<Vec<f64>> {
// 성능 집약적 로직은 Rust로
Ok(data.iter().map(|x| x * x + 2.0 * x + 1.0).collect())
}
#[pymodule]
fn rust_core(_py: Python, m: &PyModule) -> PyResult<()> {
m.add_function(wrap_pyfunction!(process_data, m)?)?;
Ok(())
}Emscripten은 C/C++ 코드를 WebAssembly로 컴파일하는 가장 오래되고 성숙한 도구 체인입니다. LLVM 기반으로, POSIX API의 상당 부분을 브라우저 환경에서 에뮬레이션합니다.
#include <emscripten.h>
#include <stdint.h>
#include <stdlib.h>
// JavaScript에서 호출할 수 있도록 export
EMSCRIPTEN_KEEPALIVE
void apply_blur(uint8_t* pixels, int width, int height, int radius) {
// 간단한 박스 블러 구현
uint8_t* temp = (uint8_t*)malloc(width * height * 4);
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
int r = 0, g = 0, b = 0, count = 0;
for (int dy = -radius; dy <= radius; dy++) {
for (int dx = -radius; dx <= radius; dx++) {
int nx = x + dx, ny = y + dy;
if (nx >= 0 && nx < width && ny >= 0 && ny < height) {
int idx = (ny * width + nx) * 4;
r += pixels[idx];
g += pixels[idx + 1];
b += pixels[idx + 2];
count++;
}
}
}
int idx = (y * width + x) * 4;
temp[idx] = r / count;
temp[idx + 1] = g / count;
temp[idx + 2] = b / count;
temp[idx + 3] = pixels[idx + 3]; // alpha
}
}
// 결과 복사
for (int i = 0; i < width * height * 4; i++) {
pixels[i] = temp[i];
}
free(temp);
}# Emscripten으로 빌드
emcc image_filter.c \
-O3 \
-s EXPORTED_FUNCTIONS='["_apply_blur","_malloc","_free"]' \
-s EXPORTED_RUNTIME_METHODS='["ccall","cwrap"]' \
-o image_filter.js
# 생성 파일: image_filter.js + image_filter.wasmEmscripten의 강점은 기존 C/C++ 프로젝트를 최소한의 수정으로 Wasm으로 포팅할 수 있다는 점입니다. FFmpeg, SQLite, OpenCV 등 주요 라이브러리들이 이미 Emscripten을 통해 Wasm으로 빌드되고 있습니다.
AssemblyScript는 TypeScript와 유사한 문법으로 WebAssembly를 직접 작성할 수 있는 언어입니다. JavaScript/TypeScript 개발자에게 가장 낮은 진입 장벽을 제공합니다.
// AssemblyScript — TypeScript와 유사하지만
// Wasm 타입에 직접 매핑됩니다
export function fibonacci(n: i32): i64 {
if (n <= 1) return n as i64;
let a: i64 = 0;
let b: i64 = 1;
for (let i: i32 = 2; i <= n; i++) {
const temp = a + b;
a = b;
b = temp;
}
return b;
}
// 문자열 처리
export function greet(name: string): string {
return "Hello, " + name + "!";
}# 설치 및 빌드
npm install assemblyscript
npx asc assembly/index.ts -o build/app.wasm --optimize
# 결과: 수 KB ~ 수십 KB (매우 작은 바이너리)AssemblyScript는 바이너리 크기가 매우 작아서 브라우저 환경에서 특히 유리합니다. 다만 TypeScript와 100% 호환되지는 않으며, nullable 타입, 클로저 등 일부 기능에 제약이 있습니다. "TypeScript처럼 생긴 별도의 언어"로 이해하는 것이 정확합니다.
Microsoft의 .NET 플랫폼은 Blazor WebAssembly를 통해 C#으로 웹 프론트엔드를 구축할 수 있습니다. .NET 런타임 자체가 Wasm으로 컴파일되어 브라우저에서 실행됩니다.
@page "/counter"
<h1>Counter</h1>
<p>Current count: @currentCount</p>
<button @onclick="IncrementCount">Click me</button>
@code {
private int currentCount = 0;
private void IncrementCount()
{
currentCount++;
}
}Blazor의 장점은 C# 개발자가 기존 지식과 라이브러리를 그대로 활용할 수 있다는 점입니다. 하지만 .NET 런타임을 포함한 초기 로딩 크기가 5~10MB에 달하므로, 로딩 최적화가 필수적입니다.
프로젝트 요구사항에 따라 적합한 언어가 달라집니다.
| 요구사항 | 추천 언어 | 이유 |
|---|---|---|
| 최소 바이너리 크기 | Rust, AssemblyScript | GC 없음, 작은 런타임 |
| 최고 성능 | Rust, C/C++ | 제로 오버헤드 추상화, LLVM 최적화 |
| 기존 C/C++ 코드 포팅 | C/C++ (Emscripten) | 최소 수정으로 마이그레이션 |
| JavaScript 팀 | AssemblyScript | TypeScript와 유사한 문법 |
| 빠른 프로토타이핑 | Python | componentize-py로 간편 빌드 |
| 풀스택 C# | .NET (Blazor) | 프론트/백엔드 언어 통일 |
| 서버사이드 + Go 팀 | TinyGo | Go 문법, 작은 바이너리 |
| 컴포넌트 모델 필수 | Rust, TinyGo, Python | 가장 성숙한 도구 지원 |
언어 선택에서 가장 중요한 요소는 팀의 기존 역량입니다. Rust가 기술적으로 최적이더라도, Go 전문 팀이라면 TinyGo가 더 현실적인 선택일 수 있습니다. Wasm의 컴포넌트 모델을 활용하면, 핵심 모듈은 Rust로, 비즈니스 로직은 팀에 익숙한 언어로 작성하는 하이브리드 접근도 가능합니다.
이번 장에서는 Rust 외 다양한 언어의 Wasm 지원 현황을 살펴보았습니다.
7장에서는 WebAssembly의 원래 영역인 브라우저로 돌아갑니다. JavaScript와 Wasm의 상호 호출, Web API 연동, 이미지/비디오 처리, AI 추론 등 브라우저에서 고성능 애플리케이션을 구축하는 방법을 다룹니다.
이 글이 도움이 되셨나요?
관련 주제 더 보기
JavaScript와 Wasm의 상호 호출, Web API 연동, 이미지/비디오 처리, AI 추론, 게임 엔진 등 브라우저에서 WebAssembly로 고성능 애플리케이션을 구축하는 방법을 다룹니다.
Rust에서 WebAssembly를 빌드하는 전체 과정을 다룹니다. wasm-pack, cargo-component, 크기 최적화, WASI 타겟 빌드, 컴포넌트 모델 적용, HTTP 핸들러 실전 예제까지.
Spin 프레임워크의 아키텍처, Fermyon Cloud와 Akamai 통합, SpinKube를 활용한 Kubernetes 배포, Docker와 Wasm의 비교를 통해 서버사이드 WebAssembly의 현재를 분석합니다.