사전 WebAssembly
표준

WebAssembly

gabury1고친 사람 github-actions[bot]

WebAssembly 는 브라우저가 자바스크립트 말고 다른 언어로 짠 프로그램도 돌리게 해 주는 웹 표준입니다. C 나 Rust 로 쓴 코드를 미리 번역해 두면 브라우저가 그것을 받아 실행합니다. 같은 방식으로 브라우저 밖 서버에서도 프로그램을 돌립니다.

쉽고 빠른 이해

WebAssembly 는 브라우저가 알아듣는 두 번째 언어입니다. 사진 편집기나 3D 게임처럼 계산이 많은 프로그램을 C 나 Rust 로 짜서 웹페이지 안에서 돌릴 때 씁니다.

이게 없으면 브라우저에서 돌릴 수 있는 것은 자바스크립트뿐입니다. 자바스크립트는 사람이 읽는 소스로 오갑니다. 값의 종류도 도는 중에 바뀔 수 있습니다. 브라우저는 실행하면서 해석과 확인을 떠안습니다.

어떻게 도나:

  1. 컴파일러가 C·Rust 소스를 .wasm 모듈 파일로 번역합니다
  2. 브라우저가 그 파일을 검사한 뒤 이 컴퓨터의 기계어로 바꿉니다
  3. 자바스크립트가 모듈이 내준 함수를 불러 씁니다
  4. 모듈은 브라우저가 떼어 준 바이트 배열 하나 안에서만 값을 읽고 씁니다

대가는 둘입니다. 모듈은 화면이나 네트워크에 직접 손대지 못해 그런 일은 자바스크립트를 거칩니다. 주고받는 값이 숫자뿐이라 문자열 같은 것은 바이트로 풀어 메모리에 써 넣어야 합니다.

그래서 짧은 함수를 자주 오가는 코드에는 오히려 손해입니다. 오래 도는 계산 한 덩이를 통째로 넘길 때 맞습니다.

상세

WebAssembly 는 프로그램을 가상 머신 한 대가 읽는 명령으로 적어 두는 웹 표준입니다. 가상 머신은 소프트웨어로 만들어 낸 기계입니다. 진짜 칩이 아니라 브라우저 안의 프로그램이 그 명령을 받아 대신 실행합니다.

이 명령은 사람이 읽는 소스가 아니라 바이트코드입니다. 바이트코드는 명령 하나를 숫자 몇 개로 적은 형식이라, 받은 쪽이 문법을 해석하지 않고 앞에서부터 읽어 나갈 수 있습니다. 브라우저는 이 명령을 이 컴퓨터의 기계어로 번역해 실행합니다.

표준을 관리하는 곳은 W3C(World Wide Web Consortium, 월드 와이드 웹 컨소시엄)입니다. W3C 는 웹 기술의 규격을 회원들이 모여 합의로 정하는 단체입니다. 문서는 명령 하나하나가 무엇을 하는지, 어떤 모듈을 받아 줘야 하는지를 정합니다. 그 문서대로 도는 실행기는 브라우저를 만드는 쪽이 각각 구현합니다.

두 번째 언어가 필요해진 까닭

브라우저가 오랫동안 알아듣는 언어는 자바스크립트 하나였습니다. 자바스크립트는 사람이 읽는 소스 그대로 페이지에 실려 옵니다. 브라우저는 그것을 받아 문법을 해석합니다. 그런 다음 돌려 보면서 기계어로 바꿉니다.

이 언어는 값의 종류를 미리 못 박지 않습니다. 같은 변수에 숫자가 들어왔다가 문자열이 들어올 수 있습니다. 그래서 실행기는 계산마다 지금 값이 무엇인지 확인해야 합니다.

확인을 건너뛰려면 실행기가 값의 종류를 추측해야 합니다. 추측이 어긋나면 해 둔 최적화를 버리고 다시 합니다.

같은 계산을 수백만 번 도는 코드는 이 되돌림에 걸리면 걸린 만큼 손해를 봅니다. 영상 변환·물리 계산·암호 연산이 그런 코드입니다. 게다가 이런 코드는 이미 C 나 C++ 로 짜인 것이 많아, 웹에 올리려면 자바스크립트로 새로 짜야 했습니다.

WebAssembly 는 두 가지를 한꺼번에 다룹니다. 값의 종류를 미리 적어 두게 해서 실행기가 확인할 것을 줄이고, 다른 언어로 짠 코드를 담을 그릇을 줍니다.

미리 번역해 둔 한 덩이

C 나 Rust 로 짠 코드는 컴파일러를 거쳐 .wasm 파일 하나가 됩니다. 이 파일을 모듈이라고 부릅니다. 모듈 안에는 함수들의 명령, 처음에 채워 둘 값, 그리고 밖에서 받아 올 것과 밖으로 내줄 것의 목록이 들어 있습니다.

페이지가 이 파일을 받으면 브라우저는 검사부터 합니다. 명령이 형식에 맞는지, 값의 종류가 어긋나지 않는지를 봅니다. 검사를 통과한 모듈만 기계어로 번역됩니다.

모듈은 설계도일 뿐이라 혼자서는 돌지 않습니다. 돌리려면 인스턴스를 만들어야 합니다. 인스턴스는 모듈에 메모리와 밖에서 받아 온 함수를 붙여 실행할 수 있게 한 한 벌입니다. 같은 모듈로 인스턴스를 둘 만들면 메모리를 서로 나눠 갖지 않습니다.

flowchart TD
    subgraph 짜는쪽["짜는 사람의 컴퓨터 · 한 번"]
        A["C · Rust 소스"] --> B["컴파일러"]
        B --> C[".wasm 모듈 파일"]
    end
    subgraph 여는쪽["페이지를 연 사람의 브라우저 · 파일을 받을 때마다"]
        D["브라우저의 검사"] --> E["기계어로 번역"]
        E --> F["인스턴스 · 메모리와 함수가 붙는다"]
    end
    C --> D

모듈에는 사람이 읽는 짝도 있습니다. 같은 내용을 괄호로 적은 WAT(WebAssembly Text format, 텍스트 형식)입니다. 개발자 도구는 이진 모듈을 이 형태로 보여 줍니다.

스택 위에서 도는 명령

WebAssembly 의 명령은 스택 위의 값을 다룹니다. 스택은 값을 위에 쌓고 위에서 꺼내는 그릇입니다. 값을 올리는 명령이 있고, 위에서 몇 개를 꺼내 계산한 뒤 결과를 다시 올리는 명령이 있습니다.

아래는 2 와 3 을 더하는 세 명령입니다. 명령 앞의 i32 는 다루는 값의 종류이고, ;; 뒤는 주석입니다. 오른쪽 주석은 그 명령이 끝났을 때 스택에 남아 있는 값입니다.

wat
i32.const 2   ;; 스택 [2]
i32.const 3   ;; 스택 [2 3]
i32.add       ;; 스택 [5]

이렇게 두면 명령마다 피연산자를 어디서 가져올지 적지 않아도 됩니다. 인자는 언제나 스택 맨 위에 있기 때문입니다. 명령이 짧아지고, 받은 쪽이 한 번 훑으며 읽어 나가기 쉬워집니다.

명령이 다루는 값의 종류는 32비트 정수, 64비트 정수, 32비트 부동소수점 수, 64비트 부동소수점 수 넷입니다. 명령 앞에 붙은 i32 나 f64 가 그중 무엇인지 말합니다. 문자열이나 객체는 명령이 직접 다루지 않고 뒤에서 볼 메모리 안의 바이트로 나타냅니다.

흐름을 바꾸는 명령도 아무 데로나 뛰지 못합니다. 분기는 블록이나 반복문처럼 미리 묶어 둔 덩이의 시작과 끝으로만 갑니다. 덕분에 브라우저는 코드를 한 번 훑는 것만으로 이 프로그램이 어디로 갈 수 있는지 전부 알 수 있습니다.

바이트 배열 하나뿐인 메모리

인스턴스가 쓰는 메모리는 선형 메모리라고 부르는 바이트 배열 하나입니다. 0번 바이트부터 번호가 매겨져 있고, 프로그램이 값을 읽고 쓰는 곳은 이 안뿐입니다. C 코드의 포인터는 이 배열 안에서 몇 번째 바이트인지를 가리키는 번호가 됩니다.

이 배열은 메모리 페이지라는 덩이 단위로 늘립니다. 앞쪽에서 말한 웹페이지와 낱말만 같고 다른 것으로, 메모리를 떼어 주는 단위를 가리킵니다. 한 페이지는 65,536바이트로 고정이고, 늘리기만 하고 줄이지는 않습니다. 이 안을 어떻게 나눠 쓸지는 모듈에 함께 들어온 메모리 관리 코드가 맡습니다.

읽고 쓰려는 번호가 배열 밖으로 나가면 실행이 거기서 멈춥니다. 다른 인스턴스의 메모리나 브라우저가 들고 있는 데이터에는 번호로 닿을 길이 없습니다. C 프로그램에서 흔한 메모리 침범이 남의 것을 건드리는 사고로 번지지 않는 까닭입니다.

다만 같은 배열 안에서는 침범이 그대로 일어납니다. 엉뚱한 곳을 덮어써도 브라우저는 그것이 잘못인지 모릅니다. 격리는 인스턴스와 바깥 사이에 그어져 있고, 인스턴스 안까지 지켜 주지는 않습니다.

호스트가 넘겨준 함수

모듈은 파일을 열거나 화면에 무엇을 그리는 명령을 갖고 있지 않습니다. 시스템 콜을 부르는 명령 자체가 없습니다. 밖에 닿는 유일한 길은 인스턴스를 만들 때 받아 온 함수를 부르는 것입니다.

무엇을 넘겨줄지는 인스턴스를 만드는 쪽이 정합니다. 모듈을 얹어 돌리는 이 바깥쪽을 호스트라고 부릅니다. 호스트가 글자를 찍는 함수 하나만 넘기면 모듈이 밖에 할 수 있는 일은 그것뿐입니다.

할 수 있는 일을 이렇게 넘겨준 것으로만 한정하는 것이 샌드박스입니다.

flowchart TD
    subgraph 호스트["호스트 · 브라우저나 서버 런타임"]
        H1["호스트가 넘겨준 함수"]
        H2["화면 · 파일 · 네트워크"]
        H1 -->|"대신 손댄다"| H2
    end
    subgraph 인스턴스["인스턴스"]
        M1["모듈의 함수"]
        M2["선형 메모리"]
        M1 -->|"읽고 쓴다"| M2
    end
    M1 -->|"밖으로 나가는 하나뿐인 길"| H1

인스턴스 밖으로 나가는 화살표는 하나뿐입니다. 호스트가 함수를 하나도 안 넘기면 모듈은 자기 메모리 안에서 계산만 하다 끝납니다.

자바스크립트와 주고받는 값

브라우저에서 모듈을 받아 인스턴스를 만드는 일은 자바스크립트가 합니다. 인스턴스가 만들어지면 모듈이 내준 함수가 자바스크립트의 보통 함수처럼 보입니다. 부르면 결과가 돌아옵니다.

주고받는 값은 숫자입니다. 문자열이나 객체는 그대로 못 건넵니다. 넘기는 쪽이 그 값을 바이트로 풀어 선형 메모리에 써 넣고, 받는 쪽에는 그 바이트가 시작하는 번호와 길이를 숫자로 알려 줍니다.

이 변환을 대신해 주는 코드를 글루 코드라고 부릅니다. Rust 나 C 쪽 도구가 모듈과 함께 만들어 주는 것이 보통이라, 실무에서는 이 변환을 손으로 짜지 않습니다.

DOM(Document Object Model, 문서 객체 모델)도 같은 길을 지납니다. DOM 은 페이지의 요소들을 프로그램이 다룰 수 있게 나무 모양으로 만들어 둔 것입니다. 모듈은 DOM 을 직접 못 만지고, 자바스크립트 함수를 불러 대신 시킵니다.

브라우저 밖의 실행 환경

모듈은 브라우저 밖에서도 돕니다. 필요한 것은 명령을 실행해 주는 런타임과 넘겨줄 함수 목록뿐입니다. 둘 다 브라우저 바깥에 둘 수 있습니다.

그런데 파일을 읽거나 시계를 보는 일은 어느 프로그램에나 필요합니다. 런타임마다 넘겨주는 함수 이름이 다르면 같은 모듈을 다른 런타임에서 못 돌립니다. 그래서 이런 함수들의 이름과 인자를 맞춰 둔 표준이 WASI(WebAssembly System Interface, 시스템 인터페이스)입니다.

이 방식은 남이 짠 코드를 대신 돌려 줘야 하는 곳에서 쓰입니다. 여러 사용자의 코드를 한 서버에서 돌리는 서버리스 실행 환경이나, 프로그램에 기능을 덧붙이는 플러그인이 그렇습니다. 컨테이너를 따로 띄우지 않고도 코드마다 할 수 있는 일을 다르게 줄 수 있습니다.

갈아탈 때 치르는 대가

이미 자바스크립트로 도는 코드를 WebAssembly 로 갈아탈 때 무엇을 잃는지를 봅니다.

자바스크립트를 오가고 값을 메모리에 써 넣는 비용이 붙습니다. 짧은 함수를 자주 오가는 코드는 이 비용이 계산에서 아낀 것보다 커지기도 합니다. 오래 도는 계산 한 덩이를 통째로 넘길 때 셈이 맞습니다.

읽기도 어려워집니다. 브라우저에 도착한 것은 이진 명령이라, 무슨 일이 일어나는지 보려면 소스와 명령을 잇는 소스 맵이 따로 있어야 합니다. 없으면 개발자 도구에 텍스트 형식만 보입니다.

파일 크기도 셈에 넣어야 합니다. 언어가 딸려 보내는 표준 라이브러리와 메모리 관리 코드가 모듈 안에 함께 들어갑니다. 작은 기능 하나를 갈아타려다 페이지가 받아야 할 파일이 더 커지기도 합니다.

관련 항목

WebAssembly 가 속하는 상위 분류

가상 머신 · 바이트코드 · 중간 표현 · 명령 집합 · 스택 머신 · 웹 표준

WebAssembly 를 정하고 관리하는 단체와 규격

W3C · WASI · Bytecode Alliance · ECMAScript · 웹 API

WebAssembly 모듈을 이루는 구성 요소

모듈 · 인스턴스 · 선형 메모리 · 테이블 · 임포트 · 익스포트 · WAT

WebAssembly 모듈을 만들어 내는 언어와 도구

C · C++ · Rust · Go · AssemblyScript · Emscripten · LLVM · 컴파일러

WebAssembly 명령을 실행하는 엔진과 런타임

브라우저 · 자바스크립트 엔진 · V8 · SpiderMonkey · Wasmtime · Wasmer · JIT 컴파일

WebAssembly 가 브라우저에서 맞물리는 웹 기술

JavaScript · DOM · 웹 워커 · ArrayBuffer · SharedArrayBuffer · WebGL · WebGPU

WebAssembly 와 같은 역할을 두고 겨루는 실행 방식

asm.js · JVM · CLR · 네이티브 코드 · 클래스 파일

WebAssembly 가 기대는 격리 장치와 보안 성질

샌드박스 · 동일 출처 정책 · 메모리 안전 · 최소 권한 · 사이드 채널

WebAssembly 를 돌리는 실행 환경

서버리스 · 엣지 컴퓨팅 · 플러그인 · 컨테이너 · 임베디드

다른 이름: 웹어셈블리 · Wasm