WebAssembly – nadciąga rewolucja

Gdy Microsoft, Mozilla i Google pracują nad jedną technologią, to wiedz że coś się dzieje…

Zanim powstało WebAssembly

Zanim przejdę do omawiania głównego tematu, opiszę pokrótce dotychczasowe rozwiązania, bo koncepcja stojąca za WebAssembly nie jest wcale nowa.

Od dłuższego czasu producenci przeglądarek internetowych pracowali nad wprowadzeniem możliwości uruchomienia skompilowanego kodu C/C++ bezpośrednio w przeglądarce. W 2011 roku, przy okazji debiutu Chrome OS, Google zaprezentowało Native Clienta (NaCl) – środowisko, w którym można było używać skompilowanego kodu C/C++. Niestety NaCl posiadał ograniczoną kompatybilność, skompilowany kod uruchamiał się tylko na maszynach z procesorami x86. Poprawiono to w kolejnej wersji o nazwie Portable Native Client (pNaCl), jednakże środowisko to nie odniosło spektakularnego sukcesu.

W roku 2013 Mozilla wprowadziła własną platformę asm.js działającą we wszystkich przeglądarkach z wydajnością zbliżoną do natywnej. Asm.js umożliwiał uruchomienie oprogramowania napisanego w C/C++ skompilowanego do JavaScriptu, zachowując wydajności znacznie lepszą niż rozwiązania napisane w standardowy sposób. Jak to możliwe? Między innymi dzięki zastosowaniu statycznego typowania oraz ograniczeniu niektórych cech języka JavaScript do podatnych na optymalizację AOT.

Kompilacja z C/C++ do asm.js miała następujący przebieg:

Kod źródłowy napisany w C/C++ zostaje skompilowany do kodu bajtowego LLVM, a następnie Emscripten kompiluje go do JavaScriptu (asm.js).

Asm.js niestety posiada też swoje wady. Pomimo, że wzrost prędkości jest znaczący, w porównaniu do zwykłego JavaScriptu, asm.js nie daje nam żadnej gwarancji, że aplikacja będzie działać z tą samą prędkością we wszystkich przeglądarkach. Spowodowane jest to brakiem otwartego standardu oraz nieformalną specyfikacją utrudniającą wdrożenie asm.js we wszystkich przeglądarkach jednakowo.

Mimo to asm.js odniósł spory sukces. Quake, Doom czy Linux przeniesione do przeglądarki internetowej to tylko nieliczne rezultaty zastosowania asm.js.

Microsoft co prawda nie prowadziło zaawansowanych prac nad własną dedykowaną platformą do kompilacji kodu z C/C++ do JavaScript, wybrało jednak nieco odmienną strategię, prezentując w 2012 roku TypeScript. TypeScript to nadzbiór języka JavaScript, który kompiluje się do standardowego JavaScriptu. Dużą zaletą TypeScriptu jest jego statyczne typowanie z zastosowaną inferencją typów.

Jak widać, wszyscy główni producenci przeglądarek internetowych posiadają swoją „alternatywę” dla języka JavaScript i najwidoczniej doszli także do wniosku, że ich dotychczasowe rozwiązania nie są wystarczająco dobre, dlatego też połączyli wspólnie siły w pracy nad WebAssembly.

WebAssembly

WebAssembly jest nowym rodzajem języka, który może być uruchomiony w nowoczesnych wersjach przeglądarek internetowych. WebAssembly, nazywany też WASM, to język niskopoziomowy, który działa z szybkością zbliżoną do rozwiązań natywnych i pozwala na kompilację kodu napisanego w C/C++ do kodu binarnego działającego w przeglądarce internetowej. Co również ważne, WebAssembly jest opracowywany jako otwarty standard webowy, nie jest w żaden sposób opatentowany ani chroniony prawami autorskimi. Nad całością projektu czuwa W3C WebAssembly Working Group.

Obecnie wsparcie dla WebAssembly prezentuje się następująco:

Aktualną tabelę można znaleźć na http://caniuse.com/#feat=wasm

Czym WebAssembly nie jest?

  • WebAssembly nie jest tworzony z myślą o zastąpieniu JavaScriptu, a raczej jego uzupełnieniu. WebAssembly jest uruchamiany w tej samej maszynie wirtualnej (VM) co JavaScript, dzięki czemu możemy czerpać korzyści z obu tych języków. WebAssembly da nam możliwość przygotowania modułów, które będziemy mogli używać w aplikacjach napisanych w Javascript.
  • Wynikowy kod nie jest kompilowany do JavaScript jak ma to na przykład miejsce w przypadku CoffeScript lub we wcześniej wspomnianym TypeScripcie lub asm.js.
  • WebAssembly to nie język programowania przeznaczony dla programistów. WebAssembly jest kodem bajtowym tworzonym przez odpowiednie narzędzia, a nie przez programistów.

Głównymi obszarami zastosowania dla WASM są:

  • gry,
  • multimedia (rozpoznawanie obrazów, edycja video, aplikacje typu CAD),
  • możliwość wykorzystania istniejących rozwiązań napisanych w C/C++ (OpenCV, Box2D, DICOM),
  • 64-bitowe obliczenia matematyczne (SHA512, Fractal Calculations).

Pierwsze kroki

Po tym przydługawym wstępie czas na trochę praktyki. Poniżej przedstawię w kilku krokach jak zacząć przygodę z WebAssembly.

1. Zacznijmy od pobrania i zainstalowania Portable Emscripten SDK (Linux & OS X version)

emsdk-portable.tar.gz

Po rozpakowaniu, z poziomu terminala wydajemy polecenia:

$ ./emsdk update
$ ./emsdk install latest

Po zainstalowaniu Emscripten SDK posiadamy cały niezbędny zestaw narzędzi, aby zacząć pracę z WebAssembly.

Gdy instalacja się zakończy, należy aktywować SDK:

$ ./emsdk activate latest
$ source ./emsdk_env.sh  # możesz dodać tę linię do .bashrc

2. Utwórzmy prosty program w C. Nasz program losuje liczbę z zakresu od 1 do 6, co odpowiada rzutowi kostką do gry.

#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <emscripten/emscripten.h>

int main(int argc, char ** argv) {
    printf("Wczytano moduł WebAssembly \n");
}

int EMSCRIPTEN_KEEPALIVE roll_dice() {
    srand ( time(NULL) );
    return rand() % 6 + 1;
}

Zapisz powyższy kod do pliku diceroll.c

3. Kompilacja z C do .wasm

$ emcc diceroll.c -O1 -o diceroll.wasm -s WASM=1
  • emcc – to kompilator Emscripten,
  • diceroll.c – plik z kodem źródłowym w C (input),
  • -01 – poziom optymalizacji kodu (więcej na ten temat w dokumentacji),
  • -o diceroll.wasm – plik z kodem WebAssembly (output),
  • -s WASM=1 – informacja dla kompilatora aby pracował w trybie WASM, w innym razie zostanie wygenerowany kod asm.js.

4. Hurra!!! Właśnie utworzyliśmy nasz pierwszy moduł w WebAssembly.

Niestety sam plik .wasm nie robi niczego spektakularnego. Przyjrzyjmy się poniższemu schematowi.

Jak widać, kod źródłowy napisany w języku C/C++ jest kompilowany do pliku .wasm. Plik ten jest modułem WASM, który dopiero za pomocą JavaScriptu dołączony zostanie do strony i przesłany do przeglądarki.

Skompilujmy zatem jeszcze raz nasz program – ale teraz użyjmy nazwy diceroll.js jako pliku wynikowego.

$ emcc diceroll.c -O1 -o diceroll.js -s WASM=1

Kompilator utworzy pliki diceroll.wasm i diceroll.js. Plik z rozszerzeniem .wasm jest modułem WebAssembly, a diceroll.js plikiem, który pozwoli nam załadować ten moduł do przeglądarki (JavaScript glue).

5. Załadujmy moduł do przeglądarki.

Utwórz plik index.html o następującej treści.

<!DOCTYPE html>
  <head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <title>WebAssembly</title>
    <meta name="viewport" content="width=device-width, initial-scale=1">
  </head>
  <body>
    <script src="diceroll.js"></script>
  </body>
</html>

6. Uruchom Web server.

Możemy posłużyć się web serwerem dostarczonym wraz z Emscripten SDK. W tym celu, będąc w katalogu roboczym, uruchom z poziomu terminala:

$ emrun --no_browser --port 8080 .

Otwórz stronę localhost:8080/index.html i zajrzyjmy do konsoli. Powinien pojawić się komunikat:

Wczytano moduł WebAssembly.

Firefox 52+ oraz Chrome 67+ mają domyślnie włączoną obsługę Webassembly, jeśli jednak używasz wcześniejszej wersji, może być konieczne ręczne włączenie obsługi Webassembly. Dla Chrome można to zrobić z poziomu chrome://flags/#enable-webassembly. W Firefoxie ustaw javascript.options.wasm na true.

7. Ostatnią rzeczą jaka została nam do zrobienia to wykorzystanie modułu WebAssembly z poziomu JavaScript.

<!DOCTYPE html>
  <head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <title>WebAssembly</title>
    <meta name="viewport" content="width=device-width, initial-scale=1">    
  </head>
  <body>
    <div class="score"></div>
    <button class="roll">Losuj</button>
    <script src="diceroll.js"></script>
    <script>
      var score = document.querySelector('.score');
      var roll = document.querySelector('.roll');
      roll.addEventListener('click', function() {
    //w tym miejscu odwołujemy się do funkcji napisanej w C
        var result = _roll_dice();
        console.log(result)
    score.innerHTML = result;
      });
    </script>
  </body>
</html>

Klikając na przycisk losuj, JavaScript odnosi się do modułu WASM w celu pobrania losowej wartości. Kod źródłowy można pobrać tutaj: diceroll.tar.gz

Działający program można znaleźć tutaj.

Podsumowanie.

Webassembly to długo wyczekiwany, kolejny kamień milowy w rozwoju przeglądarek internetowych. Przypomnijmy, że zaprojektowany w 1995 roku JavaScript nie był pisany z myślą o obsłudze dużych aplikacji internetowych. To dzięki ogromnej pracy twórców przeglądarek i wprowadzeniu bardzo wydajnych silników JavaScript (jak np. V8, SpiderMonkey) od niedawna możemy cieszyć się szybkim działaniem JavaScriptu. Jednakże nic nie może równać się z możliwością uruchomienia kodu bajtowego tak blisko „metalu”, jaką daje nam WebAssembly. Jeśli chcesz zgłębić temat wnikliwiej polecam zajrzeć na Awesome WASM.

Z zawodu i zamiłowania programista. Jara mnie wszystko, co potrafi zmienić nieciekawy input w ciekawy output, czyli m.in. programowanie, gotowanie, muzyka i fotografia.
PODZIEL SIĘ