WebAssembly의 스택 머신 실행 모델, 모듈/인스턴스/메모리/테이블 구조, 주요 런타임(Wasmtime, Wasmer, WasmEdge, V8) 비교, AOT와 JIT 컴파일 전략을 분석합니다.
WebAssembly는 스택 머신(Stack Machine) 기반의 실행 모델을 채택하고 있습니다. 레지스터 기반 아키텍처(x86, ARM)와 달리, 모든 연산이 가상 스택을 통해 이루어집니다.
간단한 덧셈 연산 (a + b) * c가 스택 머신에서 어떻게 처리되는지 살펴보겠습니다.
;; (a + b) * c를 계산
local.get $a ;; 스택: [a]
local.get $b ;; 스택: [a, b]
i32.add ;; 스택: [a+b]
local.get $c ;; 스택: [a+b, c]
i32.mul ;; 스택: [(a+b)*c]스택 머신을 선택한 이유는 명확합니다. 인코딩이 컴팩트합니다. 레지스터 번호를 지정할 필요가 없으므로 바이너리 크기가 줄어듭니다. 검증이 간단합니다. 타입 검사를 스택 상태만으로 수행할 수 있어, 모듈 로딩 시 빠르게 유효성을 확인할 수 있습니다.
실제 런타임에서는 스택 머신 명령어를 그대로 해석(interpret)하지 않습니다. 대부분의 런타임은 Wasm 바이트코드를 네이티브 코드로 컴파일하면서 레지스터 할당 최적화를 수행합니다. 스택 머신은 논리적 실행 모델일 뿐, 물리적 실행은 대상 CPU의 레지스터를 활용합니다.
WebAssembly의 실행 구조는 네 가지 핵심 요소로 구성됩니다.
**모듈(Module)**은 컴파일된 Wasm 바이너리에 해당합니다. 함수, 메모리, 테이블, 글로벌 변수의 정의와 임포트/익스포트 선언을 포함합니다. 모듈 자체는 불변(immutable)이며, 여러 인스턴스 간에 공유될 수 있습니다.
// 모듈 컴파일 (비동기)
const response = await fetch('calculator.wasm');
const bytes = await response.arrayBuffer();
const module = await WebAssembly.compile(bytes);
// 또는 스트리밍 컴파일 (더 효율적)
const module = await WebAssembly.compileStreaming(
fetch('calculator.wasm')
);**인스턴스(Instance)**는 모듈의 실행 가능한 복사본입니다. 모듈을 인스턴스화하면 메모리가 할당되고, 임포트가 바인딩되며, 익스포트된 함수를 호출할 수 있게 됩니다.
const importObject = {
env: {
log: (value) => console.log('Wasm says:', value),
memory: new WebAssembly.Memory({ initial: 1 })
}
};
const instance = await WebAssembly.instantiate(module, importObject);
const result = instance.exports.add(10, 20);1장에서 살펴본 선형 메모리입니다. 인스턴스는 최대 하나의 메모리를 가질 수 있으며(향후 다중 메모리 제안이 진행 중), 64KB 페이지 단위로 확장됩니다.
**테이블(Table)**은 함수 참조의 배열입니다. 간접 호출(indirect call)을 구현하는 데 사용됩니다. C/C++의 함수 포인터, Rust의 dyn Trait 등이 테이블을 통해 구현됩니다.
2026년 현재, WebAssembly 런타임 생태계는 성숙기에 접어들었습니다. 각 런타임은 서로 다른 설계 철학과 최적화 목표를 가지고 있습니다.
Wasmtime은 Bytecode Alliance가 개발하는 참조 구현 런타임입니다. Rust로 작성되었으며, Cranelift 코드 생성기를 사용합니다.
# Wasmtime으로 WASI 프로그램 실행
wasmtime run hello.wasm
# 파일 시스템 접근 허용
wasmtime run --dir /tmp::/ hello.wasm
# AOT 컴파일
wasmtime compile hello.wasm -o hello.cwasm
wasmtime run hello.cwasmWasmtime의 강점은 표준 준수에 있습니다. WASI Preview 2, 컴포넌트 모델 등 새로운 표준을 가장 먼저 구현합니다. Spin 프레임워크와 Fermyon Cloud의 기반 런타임이기도 합니다.
Wasmer는 다중 컴파일러 전략을 특징으로 합니다.
| 컴파일러 | 특징 | 적합한 사례 |
|---|---|---|
| Singlepass | 단일 패스 컴파일, 빠른 시작 | 개발/테스트, 짧은 실행 |
| Cranelift | 균형 잡힌 성능 | 범용 서버 워크로드 |
| LLVM | 최고 수준 최적화 | 장기 실행 연산 집약적 작업 |
Wasmer는 또한 **WAPM(WebAssembly Package Manager)**을 운영하며, Wasm 패키지 생태계 구축에 힘쓰고 있습니다.
WasmEdge는 CNCF(Cloud Native Computing Foundation) 프로젝트로, 클라우드 네이티브와 AI 추론에 최적화되어 있습니다. Kubernetes 환경에서의 Wasm 워크로드 실행, TensorFlow/ONNX 모델 추론 등에 강점을 보입니다.
Chrome, Node.js, Deno, Cloudflare Workers의 기반이 되는 V8 엔진은 가장 널리 배포된 Wasm 런타임입니다. TurboFan JIT 컴파일러와 Liftoff 베이스라인 컴파일러의 이중 구조로 빠른 시작과 높은 최적화를 동시에 달성합니다.
Wasm 런타임의 컴파일 전략은 크게 두 가지로 나뉩니다.
실행 시점에 Wasm 바이트코드를 네이티브 코드로 변환합니다. V8의 Liftoff가 대표적인 베이스라인 JIT 컴파일러입니다.
장점: 별도 빌드 단계 없이 바로 실행 가능합니다. 런타임 프로파일링 정보를 활용한 적응적 최적화가 가능합니다.
단점: 첫 실행 시 컴파일 오버헤드가 존재합니다. 메모리 사용량이 상대적으로 높습니다.
실행 전에 미리 Wasm을 네이티브 코드로 컴파일합니다. Wasmtime의 wasmtime compile 명령이 대표적입니다.
장점: 인스턴스화 시간이 극도로 짧습니다. 콜드 스타트가 약 0.5ms 수준으로, 서버리스 환경에서 결정적인 이점입니다.
단점: 대상 플랫폼별로 미리 컴파일해야 합니다. 빌드 파이프라인이 복잡해질 수 있습니다.
서버리스나 엣지 환경에서는 AOT 컴파일이 거의 필수입니다. AWS Lambda의 콜드 스타트가 수백 ms에 달하는 것과 비교하면, Wasm AOT의 0.5ms 콜드 스타트는 혁신적인 수치입니다.
Wasm 런타임을 자체 애플리케이션에 내장하는 것을 **임베딩(Embedding)**이라고 합니다. Wasmtime은 다양한 언어용 임베딩 API를 제공합니다.
use wasmtime::*;
fn main() -> Result<()> {
// 엔진 생성
let engine = Engine::default();
// 모듈 컴파일
let module = Module::from_file(&engine, "plugin.wasm")?;
// 스토어 생성 (인스턴스 상태 관리)
let mut store = Store::new(&engine, ());
// 호스트 함수 정의
let log_func = Func::wrap(&mut store, |caller: Caller<'_, ()>, msg: i32| {
println!("Plugin logged: {}", msg);
});
// 인스턴스화
let instance = Instance::new(&mut store, &module, &[log_func.into()])?;
// 함수 호출
let process = instance.get_typed_func::<(i32,), i32>(&mut store, "process")?;
let result = process.call(&mut store, (42,))?;
println!("Result: {}", result);
Ok(())
}이 패턴은 플러그인 시스템을 구축할 때 매우 유용합니다. 사용자가 제공한 Wasm 코드를 안전하게 실행하면서, 호스트 함수를 통해 필요한 기능만 선택적으로 노출할 수 있습니다.
| 요구사항 | 추천 런타임 | 이유 |
|---|---|---|
| 표준 준수, 서버리스 | Wasmtime | WASI/컴포넌트 모델 최우선 지원 |
| 다양한 최적화 옵션 | Wasmer | 3가지 컴파일러 선택 가능 |
| 클라우드 네이티브, AI | WasmEdge | CNCF 생태계 통합, AI 추론 |
| 브라우저, Node.js | V8 | 가장 넓은 배포 범위 |
| 극한의 경량화, IoT | wasm3 | 인터프리터, 수 KB 메모리 |
이번 장에서는 WebAssembly 런타임의 아키텍처를 심층적으로 분석했습니다.
3장에서는 WebAssembly를 브라우저 밖으로 꺼내는 핵심 열쇠인 **WASI(WebAssembly System Interface)**를 다룹니다. Capability-based 보안의 원리, WASI Preview 2의 구조, 그리고 비동기 지원을 위한 WASI 0.3의 로드맵을 살펴봅니다.
이 글이 도움이 되셨나요?
관련 주제 더 보기
WASI의 탄생 배경과 Capability-based 보안 모델, WASI Preview 2의 Worlds 개념, 비동기 지원을 위한 WASI 0.3, 그리고 1.0 로드맵을 상세히 다룹니다.
WebAssembly란 무엇인지, 바이너리 포맷과 텍스트 포맷의 차이, 선형 메모리 모델과 샌드박스 보안, 그리고 2026년 현재 Wasm 생태계의 전체 지도를 살펴봅니다.
WebAssembly 컴포넌트 모델의 필요성, WIT IDL의 문법과 타입 시스템, 인터페이스와 월드 정의, 컴포넌트 구성을 통한 언어 간 상호운용성을 다룹니다.