Split View: WebAssembly와 WASI 완전 해부 — 바이트코드, Sandbox, Component Model, Wasmtime, 엣지 런타임까지
WebAssembly와 WASI 완전 해부 — 바이트코드, Sandbox, Component Model, Wasmtime, 엣지 런타임까지
들어가며 — "Docker보다 작고 JVM보다 빠른"
2019년 Docker 창시자 Solomon Hykes의 트윗이 화제였다.
"If WASM+WASI existed in 2008, we wouldn't have needed to create Docker."
논란이 많은 발언이었지만, 핵심은 분명했다. Wasm은 그저 브라우저용 기술이 아니다. 2025년 지금, Cloudflare Workers, Fastly Compute@Edge, Shopify Functions, Figma 플러그인, 그리고 심지어 Kubernetes 위의 SpinKube까지 — 전부 Wasm을 엔진으로 쓴다.
이 글에서는:
- WebAssembly의 탄생 — asm.js의 그림자에서 Wasm 1.0까지
- 바이트코드 구조와 스택 가상머신 실행 모델
- Sandbox의 수학적 보증 — 왜 컨테이너보다 안전한가
- WASI — 브라우저 밖의 Wasm 시스템 인터페이스
- Wasmtime / Wasmer / WasmEdge 런타임 비교
- Component Model — 언어 중립 모듈 시스템의 비밀
- Cloudflare Workers가 왜 Isolate 대신 Wasm도 지원하는가
- Kubernetes + Wasm — runwasi, SpinKube의 그림
- 실무 함정과 미래
이전 글 HTTP/3와 QUIC 완전 해부에서 전송 계층의 재설계를 봤다. Wasm은 실행 계층의 재설계다.
1. 탄생 배경 — asm.js가 남긴 숙제
JavaScript의 불운한 수명
2010년대 초, 브라우저는 사실상 "JavaScript만 돌아가는 운영체제"였다. 하지만 JS는:
- 동적 타입 → JIT이 타입 가정을 끊임없이 재확인
- 가비지 컬렉션 지연
- 고성능 수치 연산에 부적합
asm.js (Mozilla, 2013)
답은 JavaScript를 "서브셋"으로 쓰는 asm.js였다.
function add(x, y) {
x = x|0; // 비트 OR로 정수 강제 변환
y = y|0;
return (x + y)|0;
}
|0 같은 힌트로 타입을 "미리" 확정하면, 브라우저가 기계어 코드로 최적 컴파일 가능. Emscripten이 C/C++ → asm.js로 변환해 브라우저에서 Unreal Engine이 돌았다.
하지만 asm.js는 한계가 분명했다:
- 파싱 비용 (여전히 텍스트)
- 가독성 제로
- 명세가 "비공식"
Wasm 1.0 (2017, W3C 표준)
4개 브라우저 벤더(Mozilla, Google, Microsoft, Apple)가 모여 공식 바이트코드를 설계. 2019년 W3C 공식 권고. 이름은 WebAssembly.
특징:
- 바이너리 포맷 (파싱 빠름)
- 정적 타입 기반 안전성
- 스택 기반 가상 머신
- 선형 메모리 하나 + 호출 스택 분리
2. Wasm 모듈의 해부
최소 예제
(module
(func $add (param $a i32) (param $b i32) (result i32)
local.get $a
local.get $b
i32.add
)
(export "add" (func $add))
)
WAT(WebAssembly Text format)로 작성. 컴파일하면 바이너리 .wasm. 브라우저/런타임에서 add(3, 5) 호출 가능.
모듈의 Section 구조
.wasm 파일은 섹션의 나열이다.
[0] Custom - 메타데이터 (이름, 디버그 정보)
[1] Type - 함수 시그니처 목록
[2] Import - 외부에서 가져오는 항목
[3] Function - 함수 → 타입 인덱스 매핑
[4] Table - 함수 포인터 테이블 (동적 호출용)
[5] Memory - 선형 메모리 정의
[6] Global - 전역 변수
[7] Export - 외부에 노출하는 항목
[8] Start - 모듈 초기화 함수
[9] Element - Table 초기화
[10] Code - 함수 본문 (바이트코드)
[11] Data - Memory 초기화
핵심: 선형 메모리 (Linear Memory)
Wasm 프로그램은 하나의 큰 연속된 바이트 배열을 메모리로 본다. 64KB 단위 페이지. 기본 시작 크기와 최대 크기 지정.
(memory 1 10) ;; 최소 1 페이지(64KB), 최대 10 페이지
C/C++/Rust가 "포인터"라고 부르는 것은 이 배열의 **오프셋(i32)**에 불과. 프로그램이 할당/해제를 자체 관리한다 (malloc/free도 Wasm으로 컴파일된 코드).
중요: Wasm은 이 메모리 밖으로 절대 접근할 수 없다. 호스트의 메모리에도, 다른 모듈의 메모리에도. 이게 Sandbox의 1차 방어선이다.
스택 머신
Wasm은 스택 기반 VM.
local.get $a ;; 스택 push a
local.get $b ;; 스택 push b
i32.add ;; 스택에서 둘 pop, 더해서 push
레지스터 기반(예: x86)과 달리, 스택 명령은 단순하고 인코딩이 짧다. 런타임이 이를 기계어로 JIT/AOT 컴파일할 때 최적화 여지가 크다.
3. Sandbox의 수학적 보증
왜 Wasm은 안전한가
Wasm이 제공하는 격리:
- 메모리 격리 — 선형 메모리 밖 접근 불가. 호스트도 다른 Wasm 모듈도 못 건드림
- Control Flow 무결성 — 함수 포인터는 Table을 통해서만 호출. 임의 주소로 점프 불가
- 시스템 콜 없음 — 기본 Wasm은 외부 세계에 직접 접근할 수 없음. Import된 함수만 호출 가능
- 결정론적 — 동일 입력 → 동일 출력 (부동소수점 NaN 비트 규격화 등으로 결정성 확보)
Capability 기반 보안
컨테이너는 "모든 기본 권한 ON → 취약한 것만 OFF"(deny-list 방식). Wasm은 반대. "아무 것도 못 함 → 명시적으로 Capability 전달하면 가능" (allow-list, Capability 기반).
예: 파일 읽기. Wasm 모듈은 기본적으로 파일시스템에 접근 불가. 호스트가 fd_read 함수를 import에 제공했다면, 호스트가 어떤 fd를 열어줬는지까지만 접근 가능. 경로 조작(../etc/passwd)도 호스트가 root fd를 어떤 디렉토리로 줬는지에 따라 제한.
컨테이너 vs Wasm 보안 비교
| 측면 | 컨테이너 | Wasm |
|---|---|---|
| 격리 방식 | Linux namespace/cgroup | 가상머신 + 타입 안전 |
| 기본 권한 | 많음 (deny-list) | 없음 (allow-list) |
| 탈출 공격 면 | 커널 CVE (예: Dirty COW) | VM 명세 수학적 검증 |
| 시스템 콜 | 수백 개 | 호스트가 import한 것만 |
| 시작 시간 | 100ms~수초 | 1ms 미만 |
실제로 Fastly는 Wasm에서 발견된 sandbox 탈출을 "다른 Wasm 모듈을 공격" 수준도 아닌 "Wasmtime 버그로 OOM"으로만 경험한다고 보고.
4. WASI — 브라우저 밖으로 나가다
문제: 브라우저 API만으론 서버에서 못 씀
초기 Wasm은 브라우저 API (fetch, DOM) 의존. 서버에서 파일을 읽거나 소켓을 열 표준 방법이 없었다.
WASI 0.1 (2019) — POSIX-ish
Mozilla의 Lin Clark가 주도. POSIX처럼 보이는 시스템 인터페이스를 Wasm용으로.
wasi_snapshot_preview1
- fd_read / fd_write
- fd_seek
- path_open
- clock_time_get
- random_get
- args_get / environ_get
특징: 파일 디스크립터가 호스트가 명시적으로 준 것만 쓸 수 있음 (Capability).
WASI 0.2 (2024) — Component Model 기반
0.1의 한계:
- 오직 "동기 POSIX" 모델
- HTTP/sockets 표준 부재
- 인터페이스가 C 스타일로 고정
0.2는 Component Model 위에 재구축.
wasi:http— HTTP 요청/응답 처리 표준wasi:sockets— TCP/UDPwasi:io/streams— 비동기 스트림wasi:cli— CLI 앱용 stdio
언어 중립 인터페이스 정의 (WIT).
WIT 예시
package wasi:http@0.2.0;
interface types {
variant method {
get,
post,
put,
delete,
other(string),
}
record request {
method: method,
uri: string,
headers: list<tuple<string, string>>,
body: option<body>,
}
}
이 인터페이스는 Rust, Go, JavaScript, Python 각각의 바인딩으로 생성 가능. "언어 간 표준 API".
5. Component Model — 언어 중립의 비밀
왜 모듈만으론 부족한가
Wasm 1.0 모듈은 숫자와 바이트만 주고받는다. 문자열, 배열, 구조체는 선형 메모리 오프셋으로 직접 조율해야 한다. 언어마다 규칙이 달라 상호운용이 악몽.
예: Rust → JavaScript로 문자열 전달
- Rust는 UTF-8 길이+포인터
- JS는 UTF-16
- 변환 코드를 손으로 썼다
Component Model (2024 안정화)
Wasm 위에 **"컴포넌트"**라는 상위 단위. 컴포넌트 간:
- 고수준 타입 (string, record, variant, list, option, result)
- 자동 ABI 변환
- import/export를 WIT로 선언
핵심 이점
- 언어 X로 만든 컴포넌트를 언어 Y로 만든 컴포넌트에 꽂아 사용
- 버전 관리 — WIT 인터페이스의 semver
- Registry — warg.io 같은 컴포넌트 패키지 레지스트리 출현
실제 사례:
- Rust로 만든 이미지 처리 컴포넌트를
- Go로 만든 HTTP 핸들러에서 import해서
- JavaScript로 만든 오케스트레이터가 조립
npm + Docker + gRPC의 결합이라는 평가.
6. Wasm 런타임 3대장
Wasmtime (Bytecode Alliance)
- 기본 엔진: Cranelift (Rust 컴파일러 프로젝트)
- 언어: Rust
- 특징: WASI 표준 구현의 레퍼런스
- 안정성, 보안 감사 품질 최상
wasmtime run module.wasm한 줄로 실행- Fastly Compute가 백엔드로 사용
Wasmer
- 언어: Rust
- 컴파일러 플러그러블: Singlepass(빠른 JIT), Cranelift, LLVM(최고 성능)
- wapm 패키지 매니저
wasmer run명령, 임베딩 API 다양 (C, Go, Python 등)- 클라우드 기반 Wasmer Edge 제공
WasmEdge (CNCF Sandbox)
- 언어: C++
- 특징: AI 워크로드 특화 — Tensor 연산, ONNX 지원
- Docker + Wasm 통합(runwasi)에 활발
- 많은 임베디드/IoT 환경 지원
성능 비교 (2025 Bytecode Alliance 벤치)
| 런타임 | Startup | Throughput | Memory |
|---|---|---|---|
| Wasmtime (Cranelift) | 0.3ms | 100% | 기준 |
| Wasmer (LLVM) | 2s 컴파일 / 0.1ms 재실행 | 115% | 1.3x |
| WasmEdge | 0.5ms | 95% | 0.8x |
선택 기준:
- 표준 준수 → Wasmtime
- 극한 성능 → Wasmer LLVM
- 작은 메모리 + AI → WasmEdge
7. Cloudflare Workers — Wasm이 아닌 Isolate, 그러나 Wasm도
흔한 오해
"Cloudflare Workers = Wasm"이라고 알고 있는 경우가 많다. 실은:
- 기본은 V8 Isolate — JavaScript 런타임을 경량 격리 단위로 분리
- Isolate 하나 = 약 3MB 메모리, 5ms 콜드 스타트
- Wasm은 Isolate 안에서 실행 가능
왜 Isolate + Wasm인가
- JavaScript 생태계 그대로 씀 (npm)
- 성능 크리티컬 경로는 Wasm으로
- 예: Cloudflare Pages가 Rust → Wasm으로 이미지 변환
Fastly Compute@Edge — 완전 Wasm
Fastly는 전적으로 Wasm 기반.
- 각 요청이 새 Wasm 인스턴스
- 언어: Rust, Go (TinyGo), JS, AssemblyScript
- 콜드 스타트 35μs (마이크로초!)
1밀리초 미만 콜드 스타트가 가능한 이유: Wasm AOT 컴파일 결과를 재사용하고, Sandbox가 가볍기 때문.
8. Kubernetes에서 Wasm 돌리기
runwasi 프로젝트
containerd의 shim으로 들어가 OCI 이미지로 패키징된 Wasm을 컨테이너처럼 실행. 기존 Kubernetes 생태계에 그대로 얹힘.
apiVersion: v1
kind: Pod
spec:
runtimeClassName: wasmtime
containers:
- image: ghcr.io/example/hello-wasm:latest
name: hello
SpinKube (Fermyon, 2024)
Spin은 Fermyon의 Wasm 프레임워크. SpinKube는 Kubernetes CRD로 Wasm 앱을 관리.
장점:
- Pod이 수 밀리초에 뜸
- Deno/Node보다 훨씬 작은 메모리
- Serverless가 "항상 살아있는 것처럼" 반응
단점:
- 디버깅 도구 성숙 부족
- 모든 라이브러리가 Wasm 타겟 빌드되진 않음
- WASI 0.2 네이티브 지원이 아직 과도기
KWasm (Liquid Reply)
DaemonSet으로 Wasm 런타임을 노드에 설치. 기존 Kubernetes 클러스터를 Wasm 지원 환경으로 업그레이드.
9. 언어별 Wasm 여정
Rust — 황금 표준
cargo build --target wasm32-wasiwasm-bindgen로 브라우저 JS FFIwit-bindgen로 Component Model- 크레이트 생태계의 상당 부분이 Wasm 지원
Go — TinyGo가 구원
- 표준 Go 컴파일러는 큰 런타임 포함 (GC, goroutine)
- Go 1.21부터
GOOS=wasip1 GOARCH=wasm로 WASI 공식 지원 - 하지만 바이너리 크기가 여전히 몇 MB
- TinyGo는 LLVM 기반으로 훨씬 작은 출력 (수십 KB)
- 단점: 표준 라이브러리 일부 미지원
JavaScript/TypeScript — AssemblyScript
- TypeScript 문법으로 Wasm 작성
- 빠른 학습 곡선
- 그러나 풀 JS 생태계는 못 씀
Python — Pyodide, py2wasm
- 브라우저에서 Python 돌리는 Pyodide
- CPython 자체를 Wasm으로 컴파일 (10MB+)
- 서버리스용으론 무겁지만 교육/데이터 시각화 유용
C/C++ — Emscripten, wasi-sdk
- 원래 asm.js 시대부터 지원
- Unity/Unreal 엔진도 Wasm 빌드
10. 성능 — "네이티브의 80%"가 진짜인가
이론
Wasm 바이트코드는 실행 시 JIT/AOT로 기계어. 같은 C 소스를:
- Native (gcc) vs Wasm (clang → wasm → Wasmtime AOT)
- 대부분 벤치마크에서 80~95% 네이티브 속도
지연 요소들
- 경계 검사 — 선형 메모리 접근마다 bounds check. 현대 런타임은 OS의 가상 메모리 트릭(mmap + guard pages)으로 제로 오버헤드에 근접
- SIMD — Wasm SIMD 128비트 제안이 표준화. Rust의
std::simd일부를 Wasm에 매핑 - Atomics + Threads — 공유 메모리와 아토믹 연산 지원. 웹워커로 병렬 실행
- GC 제안 — 호스트 GC에 의존하지 않는 Wasm용 GC. TypeScript/Kotlin/Java를 효율적으로 Wasm으로
현실 — 병목은 FFI
"Wasm이 네이티브보다 느리다"는 벤치마크들, 파고 보면 대부분 FFI 횟수가 많은 케이스다. 호스트 함수를 자주 호출하면 boundary 전환 비용. 해결:
- Wasm 내부에서 최대한 처리
- 배치 호출
- 공유 메모리 기반 IPC
11. 실전 사례 — 누가 어떻게 쓰는가
Figma
플러그인 시스템을 QuickJS + Wasm으로. 서드파티 코드를 브라우저에서 안전하게 실행. Plugin API 제한된 capability만 노출.
Shopify Functions
서드파티 앱이 체크아웃 로직을 커스터마이즈 가능. Rust → Wasm으로 업로드. 지연이 극도로 민감한 영역이라 Wasm의 빠른 시작이 필수.
Adobe Photoshop Web
Photoshop 데스크톱 코드를 C++에서 Wasm으로 빌드. 브라우저에서 실제 Photoshop 커널이 돌아감. Emscripten 활용.
1Password
브라우저 확장의 암호화 모듈을 Rust → Wasm으로. 보안 감사와 성능을 동시 달성.
Envoy Proxy
WASM 필터로 동적 확장. Lua/Go 필터보다 성능 좋고 언어 선택 자유. Istio의 기본 확장 메커니즘.
12. 함정과 현실적 한계
한계 1: Thread 모델
Wasm의 병렬성은 여전히 제한적. 공유 메모리 원자 연산은 가능하지만:
- 웹워커 기반이라 메시지 기반 통신이 기본
- 리턴/재진입 불가한 런타임 많음
- goroutine/async Rust의 풍부함 대비 부족
한계 2: 네트워킹
WASI 0.2가 wasi:sockets 도입했지만:
- 브라우저에서는 여전히 fetch API만 (CORS/Same-Origin)
- 서버 Wasm에서도 라이브러리 지원이 제각각
한계 3: 파일시스템 일관성
"Capability 기반 fd"는 안전하지만:
- 경로 기반 코드 대량 포팅 번거로움
- POSIX
fork()/exec()부재 (의도적)
한계 4: 디버깅
- DWARF 지원 향상 중이지만 gdb/lldb 경험은 아직 네이티브 수준 아님
- Chrome DevTools가 Wasm 디버깅 제공하지만 소스맵 한계
- 스택 트레이스 읽기 난해
한계 5: 바이너리 크기
"경량"이라지만:
- Rust hello world: ~200KB (wasm32-wasi)
- TinyGo: ~50KB
- AssemblyScript: ~10KB
- Python/CPython: ~10MB
엣지에서 콜드 스타트하려면 수십 KB가 목표인데 Rust는 panic! 체인만 해도 덩치.
13. 보안 고려사항
1. Wasm 자체는 안전하지만 import는 위험
모듈이 fd_write를 imported했다면, 호스트가 안전한 구현을 제공해야 한다. "Wasm이라서 안전"은 틀리고, **"Wasm 런타임과 호스트 바인딩이 안전해야"**가 정확.
2. Denial of Service
- 무한 루프: Fuel 또는 Epoch 기반 인터럽트로 제한
- 메모리 폭주: Memory limit 명시
- 스택 오버플로: Wasm 명세가 제한 지원
3. Side Channel
- Spectre/Meltdown 류 타이밍 공격은 Wasm 내부에서도 가능
- 런타임이 timing-precision 제한 (1ms 단위로 라운딩 등)
4. Supply Chain
- 컴포넌트 레지스트리(warg)의 서명 검증 필수
- WIT 인터페이스 호환성 검증
14. 미래 — 어디로 가는가
임박한 표준들
- WASI 0.3 — 비동기/스트림 더 풍부
- Exception Handling — try/catch 표준화
- Stack Switching — 언어별 코루틴/async
- GC Proposal — Wasm GC (이미 Chrome 119+/Firefox 120+ 지원)
- Relaxed SIMD — 플랫폼별 SIMD 최적화
Trend 예측
- 서버리스 네이티브 — Lambda처럼 기동이 중요한 영역이 Wasm으로 이동
- 플러그인 시스템 공통 언어 — Kafka Connect, VSCode 확장 등이 Wasm 기반으로 재탄생 중
- Edge AI — WasmEdge + ONNX로 모델 추론을 엣지로
- Kubernetes 포드 대안 — Wasm 워크로드가 Pod의 일부 영역 대체
- 브라우저 + 서버 통합 — 같은 Wasm 모듈을 양쪽에서 실행
한계의 실명
- JavaScript 생태계의 중력 — 당분간 JS 대체 X
- POSIX 코드베이스의 막대함 — 전부 Wasm으로 못 옮김
- 디버깅/관측성 성숙도 — 2~3년은 더 필요
15. 실무 체크리스트 12가지
- 사용 사례가 명확할 때만 도입 — 플러그인, 서버리스, 클라이언트 고성능
- 컴파일러 옵션으로 바이너리 크기 최적화 —
--release,wasm-opt -Os,strip - WASI 0.2 Component Model 기반 선택 — 미래 호환성
- Wasmtime을 레퍼런스 런타임으로 — 표준 구현 확인
- Capability 최소 원칙 — 필요한 fd만 열어줌
- Fuel / Epoch로 실행 시간 제한 — DoS 방어
wasm-opt체인 — 크기 30%+ 감소- 언어 선택은 팀 역량 기반 — Rust가 가장 성숙, TinyGo는 Go 친화
- hot path는 Wasm, cold path는 JS/호스트 — 혼합 설계
- 관측성 — 호스트가 로그/메트릭 제공 — Wasm 내부 자체 로깅은 제한
- 메모리 한계 + OOM 핸들링 — 호스트에서 감싸기
- CI에서 여러 런타임 테스트 — Wasmtime, Wasmer, WasmEdge 호환성
다음 글 예고 — CDN과 엣지 컴퓨팅의 아키텍처
Wasm이 "서버 없는 서버"의 엔진이라면, 그걸 전 세계에 뿌리는 인프라가 CDN과 엣지 네트워크다. 다음 글에서는:
- CDN의 탄생 — Akamai 1998년부터 Cloudflare, Fastly까지
- Anycast 라우팅의 마법
- 캐시 계층 전략 — tiered, pull-through, push
- 엣지 컴퓨팅의 의미 — Lambda@Edge vs Workers vs Compute@Edge
- Cache Invalidation — "컴퓨터 과학의 두 난제 중 하나"
- DDoS 방어 — L3/L4/L7, Rate Limiting, Bot Management
- 이미지/비디오 변환의 엣지 처리
- Zero Trust 네트워크 — Cloudflare Access, Access Zero Trust
- 상위 CDN 벤더 비교 (Cloudflare/Akamai/Fastly/AWS CloudFront)
"사용자가 1초 안에 페이지를 본다"는 목표 뒤에 얼마나 많은 계층이 있는지, 거기서 Wasm이 어떻게 역할을 하는지 이어서 보자.
"인터넷은 평평하지 않다. 지구의 곡률을 따라 CDN이 깔려 있고, 그 위에서 Wasm이 뛰어다닌다."
WebAssembly and WASI Deep Dive — Bytecode, Sandbox, Component Model, Wasmtime, Edge Runtimes
Intro — "Smaller than Docker, faster than the JVM"
In 2019 Docker co-creator Solomon Hykes tweeted:
"If WASM+WASI existed in 2008, we wouldn't have needed to create Docker."
Controversial, but the point stuck. Wasm is not just a browser technology. In 2025, Cloudflare Workers, Fastly Compute@Edge, Shopify Functions, Figma plugins, and even SpinKube on top of Kubernetes all run Wasm as the engine.
This article covers:
- The birth of WebAssembly — from asm.js to Wasm 1.0
- Bytecode structure and the stack-VM execution model
- The mathematical guarantees of the Wasm Sandbox
- WASI — a system interface for Wasm outside the browser
- Wasmtime / Wasmer / WasmEdge runtime comparison
- The Component Model — the secret of language neutrality
- Why Cloudflare Workers supports both Isolates and Wasm
- Kubernetes + Wasm — runwasi and SpinKube
- Real-world pitfalls and the future
1. Origins — what asm.js left behind
JavaScript's unlucky legacy
In the early 2010s the browser was effectively "an OS that only runs JavaScript." But JS has:
- Dynamic types forcing the JIT to recheck assumptions constantly
- GC pauses
- Poor fit for high-performance numeric code
asm.js (Mozilla, 2013)
The answer was asm.js — a strict subset of JavaScript.
function add(x, y) {
x = x|0; // coerce to int with bitwise OR
y = y|0;
return (x + y)|0;
}
With hints like |0, the browser could compile to machine code ahead of time. Emscripten translated C/C++ to asm.js, and Unreal Engine actually ran in the browser.
But asm.js had ceilings: parsing cost (still text), zero readability, and an "informal" spec.
Wasm 1.0 (2017, W3C standard)
Four browser vendors (Mozilla, Google, Microsoft, Apple) co-designed a real bytecode. W3C recommendation in 2019. Name: WebAssembly.
- Binary format (fast to parse)
- Static typing for safety
- Stack-based virtual machine
- One linear memory plus a separate call stack
2. Anatomy of a Wasm module
Minimal example
(module
(func $add (param $a i32) (param $b i32) (result i32)
local.get $a
local.get $b
i32.add
)
(export "add" (func $add))
)
Written in WAT (WebAssembly Text format). Compiled it becomes a binary .wasm. Host calls add(3, 5).
Module sections
A .wasm file is a sequence of sections.
[0] Custom - metadata (names, debug info)
[1] Type - function signatures
[2] Import - external bindings
[3] Function - function to type mapping
[4] Table - function pointer table (indirect calls)
[5] Memory - linear memory definition
[6] Global - globals
[7] Export - externally visible items
[8] Start - module init function
[9] Element - table initializers
[10] Code - function bodies (bytecode)
[11] Data - memory initializers
Linear memory
A Wasm program sees one big contiguous byte array as its memory. 64KB pages. Initial and maximum sizes declared.
(memory 1 10) ;; min 1 page (64KB), max 10 pages
What C/C++/Rust call a "pointer" is just an i32 offset into this array. The program manages allocation itself (malloc/free are also compiled to Wasm).
Crucially, Wasm cannot touch memory outside that array — not the host's, not another module's. That is the first line of the sandbox.
Stack machine
Wasm is a stack-based VM.
local.get $a ;; push a
local.get $b ;; push b
i32.add ;; pop two, push sum
Stack instructions are short to encode and leave plenty of room for the runtime's JIT/AOT compiler to optimize.
3. Mathematical guarantees of the sandbox
Why Wasm is safe
Isolation layers:
- Memory isolation — no access outside linear memory, including host memory and other modules
- Control-flow integrity — function pointers only through tables; no arbitrary jumps
- No syscalls by default — only imported host functions can be called
- Determinism — same input yields same output (NaN bits normalized, etc.)
Capability-based security
Containers default to "all permissions on, turn off the risky ones" (deny-list). Wasm is the opposite: nothing is allowed unless a capability is explicitly passed in (allow-list).
Example: to read a file, a Wasm module must be given fd_read as an import, and only the file descriptors the host opened are reachable. Path traversal (../etc/passwd) is bounded by which directory the host rooted.
Containers vs Wasm
| Aspect | Container | Wasm |
|---|---|---|
| Isolation | Linux namespace/cgroup | VM plus type safety |
| Default perms | Many (deny-list) | None (allow-list) |
| Escape surface | Kernel CVEs (e.g. Dirty COW) | Formally verified spec |
| Syscalls | Hundreds | Only imported ones |
| Startup | 100ms to seconds | Under 1ms |
Fastly reports that its Wasm sandbox escapes, when they appear, typically look like "Wasmtime bug causes OOM," not cross-module compromise.
4. WASI — leaving the browser
The problem
Early Wasm leaned on browser APIs (fetch, DOM). There was no standard way to read a file or open a socket.
WASI 0.1 (2019) — POSIX-ish
Led by Lin Clark at Mozilla. A POSIX-looking system interface for Wasm.
wasi_snapshot_preview1
- fd_read / fd_write
- fd_seek
- path_open
- clock_time_get
- random_get
- args_get / environ_get
File descriptors are capabilities: only what the host opened is accessible.
WASI 0.2 (2024) — on the Component Model
Limits of 0.1: synchronous POSIX only, no standard for HTTP/sockets, C-style interfaces.
0.2 is rebuilt on the Component Model.
wasi:http— request and responsewasi:sockets— TCP and UDPwasi:io/streams— async streamswasi:cli— stdio for CLI apps
Language-neutral interface definitions via WIT.
WIT example
package wasi:http@0.2.0;
interface types {
variant method {
get,
post,
put,
delete,
other(string),
}
record request {
method: method,
uri: string,
headers: list<tuple<string, string>>,
body: option<body>,
}
}
Bindings can be generated for Rust, Go, JavaScript, Python — a cross-language standard API.
5. Component Model — the key to language neutrality
Why modules are not enough
Wasm 1.0 modules only exchange numbers and bytes. Strings, arrays, and structs must be coordinated as linear memory offsets, and every language does it differently — interop is a nightmare.
Example: Rust to JavaScript string passing. Rust uses UTF-8 ptr+len; JS uses UTF-16. Glue was hand-written.
Component Model (stabilized 2024)
A higher unit called a component sits on top of Wasm. Between components:
- High-level types (string, record, variant, list, option, result)
- Automatic ABI shimming
- Imports/exports declared in WIT
Benefits
- Plug a component built in language X into one built in language Y
- Versioned WIT interfaces with semver
- Registries like warg.io for component packages
Real scenario: a Rust image component, imported by a Go HTTP handler, orchestrated by a JavaScript component — "npm + Docker + gRPC rolled into one."
6. The three major runtimes
Wasmtime (Bytecode Alliance)
- Engine: Cranelift (from the Rust compiler project)
- Language: Rust
- The reference implementation of WASI
- Top-tier stability and security audits
wasmtime run module.wasmjust works- Fastly Compute runs on it
Wasmer
- Language: Rust
- Pluggable compilers: Singlepass (fast JIT), Cranelift, LLVM (top throughput)
wapmpackage manager- Embedding APIs for C, Go, Python, etc.
- Wasmer Edge cloud offering
WasmEdge (CNCF Sandbox)
- Language: C++
- AI workloads focus — tensor ops, ONNX
- Active in Docker + Wasm (runwasi)
- Strong embedded/IoT support
Performance (2025 Bytecode Alliance bench)
| Runtime | Startup | Throughput | Memory |
|---|---|---|---|
| Wasmtime (Cranelift) | 0.3ms | 100% | baseline |
| Wasmer (LLVM) | 2s compile / 0.1ms re-run | 115% | 1.3x |
| WasmEdge | 0.5ms | 95% | 0.8x |
Pick by need: standards conformance — Wasmtime; peak throughput — Wasmer LLVM; small memory plus AI — WasmEdge.
7. Cloudflare Workers — Isolates first, Wasm too
Common misconception
"Cloudflare Workers = Wasm" is wrong. Actually:
- The base is V8 Isolates — JavaScript isolation primitives
- About 3MB memory, 5ms cold start per isolate
- Wasm runs inside the isolate
Why Isolate plus Wasm
- Keep npm and the JS ecosystem
- Use Wasm for perf-critical paths (e.g. Cloudflare Pages image conversion in Rust to Wasm)
Fastly Compute@Edge — all-in on Wasm
- One new Wasm instance per request
- Languages: Rust, TinyGo, JS, AssemblyScript
- Cold start around 35 microseconds
Sub-millisecond cold starts come from reusing AOT-compiled Wasm artifacts and the lightweight sandbox.
8. Wasm on Kubernetes
runwasi
A containerd shim that runs Wasm packaged as OCI images, so it fits into existing Kubernetes clusters.
apiVersion: v1
kind: Pod
spec:
runtimeClassName: wasmtime
containers:
- image: ghcr.io/example/hello-wasm:latest
name: hello
SpinKube (Fermyon, 2024)
Spin is Fermyon's Wasm framework; SpinKube exposes it through Kubernetes CRDs.
Pros: pods ready in milliseconds, far smaller memory than Deno/Node, serverless that feels "always on." Cons: debugging tools immature, not every library builds to Wasm, WASI 0.2 native support still transitional.
KWasm (Liquid Reply)
Installs a Wasm runtime on nodes via DaemonSet. Upgrades existing clusters to Wasm-capable.
9. Languages on Wasm
Rust — gold standard
cargo build --target wasm32-wasiwasm-bindgenfor browser JS FFIwit-bindgenfor the Component Model- Much of the crates ecosystem supports Wasm
Go — TinyGo saves the day
- Standard Go has a big runtime (GC, goroutines)
- Since Go 1.21:
GOOS=wasip1 GOARCH=wasmofficial WASI - Binaries still multiple MB
- TinyGo (LLVM-based) outputs tens of KB but drops some stdlib
JavaScript/TypeScript — AssemblyScript
- TypeScript syntax compiling to Wasm
- Gentle learning curve; full JS ecosystem unavailable
Python — Pyodide, py2wasm
- CPython compiled to Wasm (10MB+)
- Heavy for serverless, great for teaching and viz
C/C++ — Emscripten, wasi-sdk
- Supported since the asm.js era
- Unity/Unreal have Wasm builds
10. Performance — "80% of native," truly?
Theory
Wasm bytecode is JIT/AOT-compiled to machine code. For the same C source, Native (gcc) vs Wasm (clang to wasm plus Wasmtime AOT) usually lands at 80 to 95 percent of native.
Sources of overhead
- Bounds checks — modern runtimes use virtual memory tricks (
mmap+ guard pages) to get near zero cost - SIMD — 128-bit Wasm SIMD standardized; parts of Rust
std::simdmap to it - Atomics + threads — shared memory and atomics via web workers
- GC proposal — Wasm GC without relying on host GC, enabling TypeScript/Kotlin/Java
Reality — FFI is the bottleneck
Benchmarks where "Wasm beats native" or "Wasm loses to native" usually come down to FFI frequency. Calling host functions often costs boundary transitions. Mitigations: do more work inside Wasm, batch calls, share memory.
11. Who ships Wasm today
- Figma — QuickJS plus Wasm for plugins, exposing limited capabilities
- Shopify Functions — third-party checkout logic in Rust to Wasm
- Adobe Photoshop Web — C++ Photoshop kernel built with Emscripten
- 1Password — browser-extension crypto in Rust to Wasm
- Envoy Proxy — WASM filters for dynamic extension (Istio's default)
12. Traps and limits
Threading
- Still message-passing first (web workers)
- Many runtimes lack reentrant execution
- Falls short of goroutines / async Rust ergonomics
Networking
- WASI 0.2 has
wasi:sockets - Browser remains fetch-only (CORS/Same-Origin)
- Server Wasm library support varies
Filesystem consistency
- Capability-based fds are safe but porting path-based code is painful
- No POSIX
fork/exec(intentional)
Debugging
- DWARF improving, but
gdb/lldbstill behind native - Chrome DevTools Wasm support exists but source maps have limits
Binary size
- Rust hello world: about 200KB (wasm32-wasi)
- TinyGo: about 50KB
- AssemblyScript: about 10KB
- CPython: about 10MB
Edge cold starts want tens of KB; even Rust's panic chain bloats the binary.
13. Security considerations
- Wasm itself is safe, but imports are risky — safety of the bindings matters
- Denial of service — use Fuel or Epoch interrupts for loops, memory limits, stack limits
- Side channels — Spectre-style timing attacks still possible; runtimes round timers
- Supply chain — verify signatures in component registries (warg), validate WIT compatibility
14. Where things are going
Near-term standards: WASI 0.3 (richer async/streams), exception handling, stack switching (coroutines/async), GC proposal (already in Chrome 119+/Firefox 120+), relaxed SIMD.
Trends:
- Serverless-native workloads migrate to Wasm
- Plugin systems rewriting on Wasm (Kafka Connect, VSCode extensions)
- Edge AI via WasmEdge + ONNX
- Wasm workloads replacing parts of pods
- Same Wasm module running in the browser and on the server
Limits: the JavaScript ecosystem's gravity, the mass of POSIX code bases, and the 2 to 3 years still needed for debugging and observability maturity.
15. Twelve-item practical checklist
- Adopt only for clear cases — plugins, serverless, high-perf clients
- Optimize binary size —
--release,wasm-opt -Os, strip - Target WASI 0.2 Component Model for forward compatibility
- Use Wasmtime as the reference runtime
- Minimum-capability principle — only open the fds you need
- Cap runtime via Fuel / Epoch against DoS
- Chain
wasm-opt— size reductions of 30%+ - Pick the language by team skill — Rust is most mature, TinyGo is Go-friendly
- Hot path in Wasm, cold path in JS or host — hybrid by design
- Observability — surface logs/metrics from the host
- Memory limits and OOM handling wrapped by the host
- CI across multiple runtimes — Wasmtime, Wasmer, WasmEdge
Next post preview
If Wasm is the engine of "serverless without servers," CDNs and edge networks are the fabric that delivers it worldwide. Next up: the birth of CDNs, anycast routing, tiered caches, edge compute options, cache invalidation, DDoS defense, image/video processing at the edge, Zero Trust networks, and a vendor comparison across Cloudflare, Akamai, Fastly, and CloudFront.
"The internet is not flat. CDNs wrap the curvature of the Earth, and Wasm runs on top of them."