Du har helt sikkert hørt om WebAssembly, også kjent som Wasm. Du har kanskje hørt at det er noe som lar deg kjøre programmer skrevet i C/C++ og Rust i nettleseren, og derfor bestemt at det ikke er noe for deg.
Ordet Web i WebAssembly er egentlig ganske uheldig, da denne teknologien har mye å tilby også utenfor nettleseren. Den har mye å tilby til språk som i dag kompilerer til JavaScript, uansett om de er ment å kjøre i Node.JS eller i Chrome.
La oss starte med det grunnleggende: Hva er WebAssembly?
WebAssembly er et lavnivå-instruksjonsett for en virtuell maskin. I denne sammenhengen betyr ikke virtuell maskin det du får ved å bruke VirtualBox eller VMWare, men det du bruker for å kjøre Java eller C# programmer.
I motsetning til C og Rust, så kompileres ikke Java til et spesifikt instruksjonsett - også kjent som assembly - som x86 eller ARM, men til et instruksjonssett for en tiltenkt maskin som heter Java Virtual Machine.
WebAssembly faller i samme kategori, men skiller seg fra Java/C# ved at det er et mer lav-nivå instruksjonsett, og at koden i utgangspunktet er isolert fra omverdenen.
¶Fordeler med WebAssembly
Forskjellen på høy- og lavnivå er hvor mye instruksjonsettet ligner en ekte maskin.
Java sitt instruksjonssett - kalt Java Bytecode - har f.eks. støtte for arv og objekter, og mangler instruksjoner for å manipulere minne. Dette gjør det unaturlig å kompilere språk som C eller Rust til Java Bytecode, fordi disse språkene har egenskaper som ikke støttes i instruksjonsettet, deriblant minnehåndtering.
WebAssembly på sin side har ikke støtte for objekter eller arv, og du må i utgangspunktet håndtere minne selv. Siden WebAssembly er mye nærmere en fysisk maskin i sitt instruksjonsett, så vil det være relativt enkelt å kompilere C eller Rust til det. Du vil også kunne kompilere høynivåspråk som Java og Python til WebAssembly, men det vil kreve mer jobb enn f.eks. Java Bytecode.
For språk som C og Rust betyr dette at de kan kjøre overalt uten å trenge en rekompilering, til og med i nettleseren, og med tilsvarende ytelse og minneffektivitet. Du kan også blande WebAssembly og JavaScript. Hvis du vil lage en konkurrent til Photoshop kan du f.eks. lage grensesnittet i JavaScript og React, mens selve bildeprosesseringen skrives i Rust for optimal ytelse.
Språk som i dag kompilerer til JavaScript kan også få store fordeler ved å kompilere til WebAssembly istedenfor. Du får ikke nødvendigvis bedre ytelse og lavere minnebruk, men du vil absolutt ha potensial til å oppnå det. I tillegg er WebAssembly et mye mer kompakt format enn JavaScript, så det er store muligheter for at programmene blir mindre og starter opp raskere.
WebAssembly har også en annen stor fordel som gjør det godt egnet i og utenfor nettleseren: isolasjon.
Med WebAssembly har du i utgangspunktet ingen tilgang på omverdenen. Alt du kan gjøre er å regne ut ting. For å kunne gjøre et nettverkskall eller logge en linje med tekst, så må slike funksjoner bli gjort tilgjengelig ved programstart. Til og med minne er noe WebAssembly programmet må få tilgjengeliggjort.
Fordelen med dette er sikkerhet. Du kan motta et WebAssembly program fra en bruker og være sikker på at det programmet ikke kan skrive til filsystemet eller bruke mer enn 16MB minne, så lenge du ikke gir programmet tilgang på slike ressurser.
Dette betyr at WebAssembly kan brukes som et plugin system i programmet ditt, til å implementere moduler i Linux kjernen, eller som en Docker-erstatning.
¶Hvordan ser WebAssembly ut?
Det finnes to formater: et binærformat og et tekstformat. Tekstformatet er i utgangspunktet ment for debugging eller for når mennesker skal skrive WebAssembly direkte. I sistnevnte tilfelle så finner du egne programmer som kan konvertere mellom tekst- og binærformatet.
En enkel funksjon for å regne ut kvadratet av et tall ser slik ut:
(module
;; Definer en funksjon, kalt square, som tar inn en 32-bit int
;; og returnerer en 32-bit int
(func $square (param i32) (result i32)
(local.get 0) ;; Last inn første parameter
(local.get 0) ;; Last inn første parameter igjen
i32.mul ;; Erstatt de to foregående verdiene med produktet av dem
return ;; returner gjenværende verdi
)
;; Tilgjengeliggjør funksjonen square under navnet square
(export "square" (func $square))
)
Som du ser så har tekstformatet til WebAssembly – kjent som WAT – en Lisp-aktig syntax. Hvis du ser litt nærmere så ser du også at dette ikke akkurat er Lisp. På lik linje med Javas virtuelle maskin, så er WebAssembly maskinen stack-basert.
Hva betyr det?
Se for deg en stabel med tallerkner. Hvis du skal legge til nye tallerkner i stabelen, så vil du mest sannsynlig gjøre det ved å legge nye tallerkner på toppen av de som allerede er der. På samme måte vil du også ta vekk fra toppen om det er det du ønsker. Verdier i WebAssembly fungerer litt på samme måte. Vi har ikke navngitte variabler som vi kan lagre verdier i, istedenfor legges verdier av og på en stabel, eller en stack.
For å regne ut kvadratet av et tall må vi først laste det tallet inn to ganger, slik at verdien forekommer to ganger i stabelen vår. Når vi så kaller multiplikasjonsinstruksen i32.mul, så plukkes disse to verdiene av stabelen og produktet legges tilbake. Til slutt kan vi altså returnere dette produktet fra funksjonen.
Det er verdt å nevne her at WebAssembly har et enkelt typesystem. Hvis du har ubrukte verdier, eller verdier av feil type, i stabelen din så vil du få en kompileringsfeil.
For å kjøre denne funksjonen så må vi først gjøre det om til binærformatet. Da kan vi bruke programmet wat2wasm
som finnes i npm pakken kalt wabt
(web assembly binary toolkit).
For å kalle square funksjonen vår fra JavaScript må vi laste inn WebAssembly programmet og kjøre det:
const fs = require("node:fs");
const bytes = fs.readFileSync("index.wasm");
WebAssembly.instantiate(bytes, {}).then(module => {
console.log(
module.instance.exports.square(4)
);
})
Dette er bare et enkelt eksempel. Hvis du vil lære mer avansert WebAssembly så anbefaler jeg å lese WebAssembly By Hand.
¶Oppsummering
Skaperen av Docker har sagt at hvis WebAssembly fantes tidligere, så hadde det ikke vært bruk for Docker idag.
Du skulle kanskje tro at en teknologi som kunne erstatte Docker ville vært overalt i dag, likevel er det de færreste som kjenner til teknologien. Det er heller ikke mange som benytter seg av muligheten til å bruke Rust eller tilsvarende språk for frontendutvikling.
Nylig ble WebAssembly 2.0 spesifikasjonen ferdigstilt. Kanskje endringene her vil gjøre at WebAssembly for alvor tar av.
Eller kan det hende at mulighetene WebAssembly gir, ikke er så nyttige som en først skulle tro?