원문
Nick Larsen, http://cultureofdevelopment.com/blog/build-your-first-thing-with-web-assembly/
처음으로 웹어셈블리(WebAssembly)에 대해 들었을 때 그것은 확실히 멋져 보였고 시도해 본다는 것에 매우 흥분했다. 그래서 곧바로 시작했지만 수많은 좌절을 겪게 되었다. 이 글의 목표는 당신에게 내가 겪었던 좌절의 일부만이라도 겪지 않게 하는 것이다.
이 포스트는 2016년 6월 24일에 쓰였다. 웹어셈블리는 매우 새롭고 불안정한 기술이며 모든 브라우저를 위한 표준화 과정이 계속되고 있기 때문에 이 포스트의 모든 것은 잘못된 것일 수 있다.
공식 웹사이트에는 다음과 같이 설명하고 있다.
웹어셈블리는 웹에서 포터블하게 컴파일되기에 알맞은 크기와 효율적인 로딩 시간을 갖고 있는 새로운 형식이다.
형식? 문자? 바이너리? 솔직히 말하면 그냥 나쁜 설명일 뿐이다. 대신 웹어셈블리에 대한 경험을 바탕으로 이해하기 쉽게 설명해보겠다.
웹어셈블리는 성능을 고려한 브라우저 웹 컴포넌트를 작성하기 위한 바이트코드 명세서다.
여전히 무언가 부족한듯하다. 그러나 아직 끝난 게 아니다. 웹어셈블리는 정적 타입 변수 사용으로 성능 향상을 실현한다. 정적 타입 변수는 런타임 시에 동적 타입 변수 보다 참조에 대해 훨씬 더 효율적이다. 웹어셈블리는 최종적으로 모든 사양의 브라우저에서 호환을 위해 W3C 커뮤니티 그룹(W3C Community Group)에서 개발되고 있다. 그리고 가장 중요한 사항은, 최종적으로 어떤 언어로도 이 웹 컴포넌트를 개발할 수 있다는 점이다.
정말 놀랍지 않은가?
새로운 것을 배울 때 보통은 동작을 볼 수 있는 가장 작은 예제를 살펴본다. 유감스럽게도 웹어셈블리에서는 현실적으로 불가능하다. 현재 웹어셈블리는 바이트코드에 대한 기본적인 명세서만 갖고 있다. 1996년으로 돌아가서, 썬 마이크로시스템의 일부 개발자들이 자바가 없는 JVM을 소개한다고 가정하면, 다음과 같은 대화를 상상할 수 있다. "이봐 너희들~ 우리가 만든 가상머신을 실행하는 이 바이트코드를 와서 보라고!" "와우~ 코드가 궁금한데?"
HelloWorld 바이트코드
"으음… 그, 그래. 시간날 때 한번 살펴 볼게." "좋아, 혹시 의견이 있거나 문제를 겪게 된다면 깃헙 페이지에 글을 남겨줘." "그래, 여기에 다른 프로젝트들이 몇개 올라오면 확인해 볼게."
JVM이 자바언어 기반이기 때문에 좋은 예제는 아니지만 그래도 이 예제를 통해 중요한 부분을 알게 되었다. 그것은 당신이 작성한 바이너리 코드가 바로 컴파일되서 보여지지 않으면, 시작부터가 굉장히 힘들어질 거란 것이다. 그렇다면 실제로 어떻게 시작해야 할까?
대부분의 기술은 혁신의 결과다. 특히 합리적인 시도가 공식적인 명세서로 만들어지게 되는 경우라면 더욱 그렇다. 웹어셈블리 또한 정적 타입으로 컴파일 되는 자바스크립트 컴포넌트 개발 명세서인 asm.js의 구현을 효과적으로 이어간다.
웹어셈블리는 asm.js의 아이디어를 확장하여 어떤 언어의 컴파일러에서도 사용될 수 있고 텍스트 인코딩 대신 이진 파일 형태로 전송될 수 있는 바이트코드 명세를 작성하였고, 시간이 지나면서 모질라 뿐만 아니라 많은 주요 브라우저의 대표자들에 의해 발전되었다. asm.js는 단지 언어 기능의 최소 집합을 사용하는 자바스크립트를 작성하기 위한 명세서일 뿐이다. 만약 수작업으로 단순한 asm.js를 작성할 수 있고, 고된 수작업을 원한다면 그것이 바로 확실히 시작하는 방법이다.(나중을 위해 파일에 저장하면서 하는 것이 가장 좋으며 명명 규칙은 [모듈명].asm.js이다.)
function MyMathModule(global) {
"use asm";
var exp = global.Math.exp;
function doubleExp(value) {
value = +value;
return +(+exp(+value) * 2.0);
}
return { doubleExp: doubleExp };
}
위의 코드는 특별히 유용하지는 않지만 스펙에 준한 함수다. 이상해 보이겠지만 위의 예제에 있는 대부분의 모든 문자가 필요하다. 모든 +
단항 연산자는 컴파일러에서 타입 주석처럼 행동하는데, 이는 +
연산자가 double 타입임을 나타내며 이로 인해 런타임에서는 판별하는 일을 하지 않는다.
매우 조심해서 사용해야 하지만 그르칠 경우에는 파이어폭스 콘솔에서 해결할 수 있는 적절한 에러 메시지를 줄 것이다.
아래와 코드를 브라우저에서 사용하게 되면,
var myMath = new MyMathModule(window);
for(var i = 0; i < 5; i++) {
console.log(myMath.doubleExp(i));
}
올바른 경우에는 다음과 같은 출력을 보일 것이다.
이제 우리는 asm.js의 작업 조각을 가지고 웹어셈블리로 컴파일 하기 위해 웹어셈블리 github 페이지에서 제공하는 도구를 사용할 수 있다. 지금 바로 레파지토리에서 클론(clone)하고 자신의 도구로 빌드할 수 있다. 이 도구는 아직도 개발 중에 있으며, 특히 윈도우 환경에서는 수시로 죽는 것이 일반적인 현상이다.
시스템이 윈도우든 맥이든지 간에 설치되어있는 커멘드라인 도구로 make
와 cmake
실행을 해야 한다. 윈도우에서는 visual studio 2015도 설치되어 있어야 한다.
맥 사용자라면 Binaryen Building에 대한 소개를,
윈도우 사용자라면 Compiling the Binaryen Toolchain under Windows with Visual Studio 2015 소개를 참고하기 바란다.
윈도우에서 binaryen 생성 하기
작업 중인 바이너리를 배포하는 것은 웹어셈블리 팀에 큰 도움이 된다.
만약 여기까지 성공적으로 따라왔다면, 웹어셈블리로 우리의 asm.js를 변환하기 위해 사용할 수 있는 몇몇 도구가 포함된 bin 폴더가 binaryen 디렉토리에 만들어질 것이다.
첫 번째 도구는 asm2wasm.exe
실행 파일이다. 이 도구는 asm.js 코드를 .s
포맷 코드로 컴파일하는데, 이는 웹어셈블리 출력을 위한 추상 구문 트리(AST, Abstract Syntax Tree)의 텍스트 표현이다.
유틸리티를 실행하면, 결국 다음과 같은 코드를 보게 된다.
(역자주: 위의 MyMathModule
함수 생성 예제로 [모듈명].asm.js
파일 생성 후 asm2wasm [모듈명].asm.js
과 같이 실행해봅니다.)
(module
(memory 256 256)
(export "memory" memory)
(type $FUNCSIG$dd (func (param f64) (result f64)))
(import $exp "global.Math" "exp" (param f64) (result f64))
(export "doubleExp" $doubleExp)
(func $doubleExp (param $0 f64) (result f64)
(f64.mul
(call_import $exp
(get_local $0)
)
(f64.const 2)
)
)
)
미래에는 라인 단위로까지 나눌 수 있지만, 지금은 단지 웹어셈블리가 이진 형식이라는 것에 대해 보여주고 얘기하는 것에 만족한다. 이진 형식이기 때문에 동작하지 않는 자바스크립트에서 우클릭 소스 보기를 할 경우, 위의 성공한 경우와 바이트 코드까지도 매우 비슷한 소스를 보게 될 것이다. 현재 계획은 당신이 웹어셈블리 모듈에서 소스를 볼 때, 사람이 읽기 가능하게 만들어진 포맷으로 역어셈블한 소스를 보여주는 것이다.
다음으로 .s
포맷 코드를 웹어셈블리 바이너리로 변환하고 웹어셈블리 어셈블러인 wasm-as.exe
를 사용해야 한다.
이 파일을 한번 실행하면 브라우저를 위해 필요한 실제 웹어셈블리 바이트코드(bytecode)를 얻을 것이다.
ams.js를 웹어셈블리 바이너리로 변환
웹어셈블리 바이트코드
다음으로, 파이어폭스 마지막 버전이나 크롬 카나리에서 웹어셈블리를 활성화한다.
파이어폭스의 경우 url 영역에 about:config
를 입력하여 이동한 후 검색 바에 wasm
을 입력하고 결과로 나오는 javascript.options.wasm
을 더블클릭하여 true
로 설정한 후 브라우저를 재시작 하면 설정이 완료된다.
크롬 카나리의 경우 chrome://flags
를 입력하여 설정화면으로 이동한 뒤 스크롤을 내려 Experimental WebAssembly
를 찾은 후 '사용'링크를 클릭하여 활성화 시킨 후 브라우저를 재시작하면 설정이 완료된다.
마지막 단계는 브라우저에서 실행 할 모듈을 얻는 것이다. 이것은 내가 최초로 시작했을 때 완전히 숨겨져 있었기 때문에 나에게 또 다른 고통을 주었다. 웹어셈블리 모듈을 사용하기 위한 자바스크립트 API와 관련된 스팩의 어떤 것도 할 수 없었다. 크롬 카나리에서 콘솔 창을 열고 WebAsse
라고 입력했을 때 어떤 자동완성 팝업도 뜨지 않았다.(역자주: 현재는 WebAssembly라는 항목이 뜸)
이어서 Was라고 다시 입력했고 드디어 자동완성 팝업이 떴다! 인스펙터에서 해당 객체의 내용은 너무 빈약했지만 그것이 웹어셈블리로 컴파일하는 몇몇 다른 도구(emscripten)를 사용하게 된 계기가 되었다.
내가 했던 방법을 다른 블로그 게시물의 주제로 작성했으나 그 후에야 동작하는 예제를 생성할 수 있었다.
얼마 뒤 나는 온갖 자료를 다 뒤져 결국 웹어셈블리를 설계 레파지토리를 찾아냈다. 그곳에는 JS.md라는 이름의 파일에 실제 자바스크립트 API 문서가 있었다. JS.md 파일에서 상단 이탤릭체로 작성되어있는 부분은 세심히 주의해서 보아야 한다. 그러나 제일 중요한 부분은 매우 작은 모듈을 로드하는 방법을 보여주는 거의 맨 밑에 있는 코드 조각이다. 나는 알고 있는 모든 관련 지식을 내려놓고 도전했다.
fetch("my-math-module.wasm")
.then(function(response) {
return response.arrayBuffer();
})
.then(function(buffer) {
var dependencies = {
"global": {},
"env": {}
};
dependencies["global.Math"] = window.Math;
var moduleBufferView = new Uint8Array(buffer);
var myMathModule = Wasm.instantiateModule(moduleBufferView, dependencies);
console.log(myMathModule.exports.doubleExp);
for(var i = 0; i < 5; i++) {
console.log(myMathModule.exports.doubleExp(i));
}
});
위의 코드를 포함하여 html을 생성하고 로컬 폴더에서 서버로 올린 후 그것을 로드 해보자.
두 개의 브라우저에서 보여준 결과가 아래에 있다.
브라우저에서의 웹어셈블리 동작(또는 최소한의 시도)
아마도 버그를 보고해야 할 것 같다. 그것들은 모두 실험적인 기술이며 매우 불안정하기에 이런 일이 발생했을 때 너무 좌절하지 않으려고 노력해야 한다는 것을 기억하자.
당신의 첫 번째 어셈블리 컴포넌트를 완성했다. 다음은 무엇인가? 여기서는 정말 표면만 살펴봤다. 수작업으로 asm.js을 작성하는 것은 이 예제에서 매우 중요하지만 중대한(non trivial) 작업을 하는 것은 오랜 시간과 많은 인내심을 필요로 한다. 그런 의미에서, 나는 asm.js 명세서 특히 메모리 모델을 읽을 것을 강하게 제안한다. 왜냐하면 많은 컨셉들이 메모리 모델로부터 웹어셈블리로 이어지고 있기 때문이다. 또 다른 특이점은 지금 당신이 직접 함수 인자로 배열을 전달할 수 없다는 것이다. 이것이 수정되어야 한다는 몇몇 의견들이 있지만, 아직 명세로서 만들어진 것은 없다. 포인터 다루는 기술을 되살려야 한다.
다른 얘기인데, 웹어셈블리에서 중대한 작업을 시작할 때, 웹어셈블리에서는 구식 자바스크립트 보다 사실상 느리게 수행하는 것을 알게 될 것이다. 현대 자바스립트 엔진들은 자바스크립트 컴파일에 매우 최적화되어있고 웹어셈블리 컴파일이 따라가는데 시간이 걸릴 것이라는 것을 기억하자. 웹어셈블리는 상용으로 쓰기에는 아직 준비되지 않았다.
만약 웹어셈블리 또는 여기에서 설명한 도구에 대해 다른 의문점이 있다면, 스택오버플로를 통해 문의하길 바란다.