A WebAssembly

tale

2022

WebAssembly ?

Web + Assembleur ?

?!?!?

Nicolas Decoster

@ogadaki

Toulouse

"WebAssembly or wasm is a new portable, size- and load-time-efficient format suitable for compilation to the web."

portable

efficient

compilation to the web

webassembly.org :

fast!

safe!

?

"WebAssembly (abbreviated Wasm) is a binary instruction format for a stack-based virtual machine. Wasm is designed as a portable compilation target for programming languages, enabling deployment on the web for client and server applications."

webassembly.org (now) :

WebAssembly, pour faire quoi ?

jeux

édition de vidéo ou d'image

application musicale

reconnaissance d'image

VR et AR

visualisation et simulation scientifique

etc.

vote du logo :

1990

HTML

HTTP

JavaScript

Web 2.0

1995

1996

2002

Flash

contenu statique

en ligne

naviguable

contenu dynamique

omniprésent

orienté utilisateur

besoin de plus de dynamique côté navigateur

comment ?

standard : JavaScript

mal aimé et lent

mais...

nombreuses tentatives d'alternatives

mais...

Flash, Java, ActiveX, NaCL, SilverLight, Dart...

problèmes de sécurités, de standardisation, d'adoption...

aucune techno ne s'impose vraiment

flash a un certain succès

mais pas adopté complétement

?

2012

Alon Zakai - Mozilla

side project : compiler du C en JavaScript

Comment ? Avec clang et LLVM !

ça marche !

code

C ou C++

LLVM

bytcode

clang

code

JavaScript

emscripten

Mais le code JavaScript généré est lent

comment améliorer les performances ?

d'autres rejoignent le projet chez Mozilla

-> asm.js !

asm.js = "a highly optimizable subset of JavaScript

+ a very large array as memory "

bonnes performances !

premier test grandeur nature : un jeu, la demo de Citadel d'Epic (basé sur le moteur Unreal 3). Au bout d'une semaine ça compile en asm.js, ça tourne et c'est fluide.

function strlen(ptr) {
  ptr = ptr|0;
  var curr = 0;
  curr = ptr;
  while (MEM8[curr]|0 != 0) {
    curr = (curr + 1)|0;
  }
  return (curr - ptr)|0;
}

code

C ou C++

LLVM

bytcode

clang

code

asm.js

emscripten

Milestone

Il est maintenant possible

de compiler du C/C++ pour le Web

performant

(dans certains navigateurs)

fonctionne dans tous les navigateurs

sans plugin

grâce à emscripten

grâce à asm.js

car ams.js = JS subset

bonus gratuit : c'est sécurisé

car dans le moteur JS

mai 2013

bases posées

asm.js est utilisable

aucun obstacle à une adoption large

preuve de concept fonctionnelle

Mozilla se tourne vers les autres acteurs pour lancer une nouvelle dynamique commune sur ces acquis

WebAssembly

crédit:        @linclark         code-cartoons.com

projet WebAssembly

feuille blanche

nouvelle techno dans la lignée d'asm.js

intégrant l'expérience des équipes derrière asm.js et de technos similaires

+ contraintes bien pensées

+ adoption large par les acteurs

= succès

+ retours d'expérience

april 2015        WebAssembly Community Group started

 

june 2015         The first public announcement

 

march 2016     Definition of core feature with multiple interoperable
                           implementations

 

october 2016   Browser Preview announced with multiple interoperable
                           implementations

 

march 2017      Firefox and Chrome support WebAssembly

webassembly.org :

WebAssembly,

pour les dévs

j'ai un code en C

je veux le compiler en WebAssembly

Comment ?

j'ai un code en Rust

je veux le compiler en WebAssembly

Comment ?

tooling de la communauté Rust

j'ai un code en C#

je veux le compiler en WebAssembly

Comment ?

Blazor

= prochaine version de .NET Core

.NET 5

j'ai un code en Go

je veux le compiler en WebAssembly

Comment ?

natif avec le compilateur

j'ai un code en Java

je veux le compiler en WebAssembly

Comment ?

Tu attends (ou pas)...

j'ai un code en Python

j'ai un code en etc.

https://github.com/appcypher/awesome-wasm-langs

2019

old

#include <stdio.h>
int main() {
    printf("WebAssembly\n");
}
$ ls
cli.js  cli.wasm  main.c
$ emcc main.c -o cli.js
$ ls
cli.js  cli.wasm  main.c
$ node cli.js
WebAssembly
#include <stdio.h>
int main() {
    printf("WebAssembly\n");
}
$ ls webapp.*
webapp.html  webapp.js  webapp.wasm
$ emcc main.c -o webapp.html

   WebAssembly

$ ls webapp.*
webapp.html  webapp.js  webapp.wasm
$ firefox webapp.html

s'ocuppe du sale boulot à votre place

compilation du C

émission du binaire WebAssembly

création du code JavaScript nécessaire

ajout de dépendances (stdio...)

etc.

liens avec canvas et WebGL

Et sous le capot ?

un format binaire

moteur WebAssembly dédié dans le navigateur

interaction avec le moteur JavaScript

format WebAssembly

(module
  (table 0 anyfunc)
  (memory $0 1)
  (export "memory" (memory $0))
  (export "_Z4facti" (func $_Z4facti))
  (func $_Z4facti (param $0 i32) (result f64)
    (local $1 i64)
    (local $2 i64)
    (block $label$0
      (br_if $label$0
        (i32.lt_s
          (get_local $0)
          (i32.const 1)
        )
      )
      (set_local $1
        (i64.add
          (i64.extend_s/i32
            (get_local $0)
          )
          (i64.const 1)
        )
      )
      (set_local $2
        (i64.const 1)
      )
      (loop $label$1
        (set_local $2
          (i64.mul
            (get_local $2)
            (tee_local $1
              (i64.add
                (get_local $1)
                (i64.const -1)
              )
            )
          )
        )
        (br_if $label$1
          (i64.gt_s
            (get_local $1)
            (i64.const 1)
          )
        )
      )
      (return
        (f64.convert_s/i64
          (get_local $2)
        )
      )
    )
    (f64.const 1)
  )
)
0000000: 6100 6d73 0001 0000 0601 6001 7f01 7c01
0000010: 0203 0001 0404 7001 0000 0305 0001 0701
0000020: 0215 6d06 6d65 726f 0279 0800 5a5f 6634
0000030: 6361 6974 0000 3f0a 3d01 0201 027e 2040
0000040: 4100 4801 000d 0020 42ac 7c01 0121 0142
0000050: 0221 4003 0220 0120 7f42 227c 7e01 0221
0000060: 0120 0142 0d55 0b00 0220 0fb9 440b 0000
0000070: 0000 0000 3ff0 000b
0000000: 0061 736d            # WASM_BINARY_MAGIC
0000004: 0100 0000            # WASM_BINARY_VERSION
# section "Type" (1)
0000008: 01                   # section code
0000009: 00                   # section size (guess)
000000a: 01                   # num types
# type 0
000000b: 60                   # func
000000c: 01                   # num params
000000d: 7f                   # i32
000000e: 01                   # num results
000000f: 7c                   # f64
0000009: 06                   # FIXUP section size
# section "Function" (3)
0000010: 03                   # section code
0000011: 00                   # section size (guess)
0000012: 01                   # num functions
0000013: 00                   # function 0 signature index
0000011: 02                   # FIXUP section size
# section "Table" (4)
0000014: 04                   # section code
0000015: 00                   # section size (guess)
0000016: 01                   # num tables
# table 0
0000017: 70                   # anyfunc
0000018: 00                   # limits: flags
0000019: 00                   # limits: initial
0000015: 04                   # FIXUP section size
# section "Memory" (5)
000001a: 05                   # section code
000001b: 00                   # section size (guess)
000001c: 01                   # num memories
# memory 0
000001d: 00                   # limits: flags
000001e: 01                   # limits: initial
000001b: 03                   # FIXUP section size
# section "Export" (7)
000001f: 07                   # section code
0000020: 00                   # section size (guess)
0000021: 02                   # num exports
0000022: 06                   # string length
0000023: 6d65 6d6f 7279       # export name
0000029: 02                   # export kind
000002a: 00                   # export memory index
000002b: 08                   # string length
000002c: 5f5a 3466 6163 7469  # export name
0000034: 00                   # export kind
0000035: 00                   # export func index
0000020: 15                   # FIXUP section size
# section "Code" (10)
0000036: 0a                   # section code
0000037: 00                   # section size (guess)
0000038: 01                   # num functions
# function body 0
0000039: 00                   # func body size (guess)
000003a: 01                   # local decl count
000003b: 02                   # local type count
000003c: 7e                   # i64
000003d: 02                   # block
000003e: 40                   # void
000003f: 20                   # get_local
0000040: 00                   # local index
0000041: 41                   # i32.const
0000042: 01                   # i32 literal
0000043: 48                   # i32.lt_s
0000044: 0d                   # br_if
0000045: 00                   # break depth
0000046: 20                   # get_local
0000047: 00                   # local index
0000048: ac                   # i64.extend_s/i32
0000049: 42                   # i64.const
000004a: 01                   # i64 literal
000004b: 7c                   # i64.add
000004c: 21                   # set_local
000004d: 01                   # local index
000004e: 42                   # i64.const
000004f: 01                   # i64 literal
0000050: 21                   # set_local
0000051: 02                   # local index
0000052: 03                   # loop
0000053: 40                   # void
0000054: 20                   # get_local
0000055: 02                   # local index
0000056: 20                   # get_local
0000057: 01                   # local index
0000058: 42                   # i64.const
0000059: 7f                   # i64 literal
000005a: 7c                   # i64.add
000005b: 22                   # tee_local
000005c: 01                   # local index
000005d: 7e                   # i64.mul
000005e: 21                   # set_local
000005f: 02                   # local index
0000060: 20                   # get_local
0000061: 01                   # local index
0000062: 42                   # i64.const
0000063: 01                   # i64 literal
0000064: 55                   # i64.gt_s
0000065: 0d                   # br_if
0000066: 00                   # break depth
0000067: 0b                   # end
0000068: 20                   # get_local
0000069: 02                   # local index
000006a: b9                   # f64.convert_s/i64
000006b: 0f                   # return
000006c: 0b                   # end
000006d: 44                   # f64.const
000006e: 0000 0000 0000 f03f  # f64 literal
0000076: 0b                   # end
0000039: 3d                   # FIXUP func body size
0000037: 3f                   # FIXUP section size

format binaire

format texte

(s-expression)

format binaire avec commentaires

WebAssembly, le langage.

(module)
(module
  (func $getValue)
)
(module
  (func $getValue (result i32)
    i32.const 38)
)

exemple minimal

(module
  (func $getValue (result i32)
    i32.const 38)
)
0000000: 0061 736d               # WASM_BINARY_MAGIC
0000004: 0100 0000               # WASM_BINARY_VERSION
# section "Type" (1)
0000008: 01                      # section code
0000009: 05                      # section size
000000a: 01                      # num types
# type 0
000000b: 60                      # func
000000c: 00                      # num params
000000d: 01                      # num results
000000e: 7f                      # i32
# section "Function" (3)
000000f: 03                      # section code
0000010: 02                      # section size
0000011: 01                      # num functions
0000012: 00                      # function 0 signature index
# section "Code" (10)
0000013: 0a                      # section code
0000014: 06                      # section size
0000015: 01                      # num functions
# function body 0
0000016: 04                      # func body size
0000017: 00                      # local decl count
0000018: 41                      # i32.const
0000019: 26                      # i32 literal
000001a: 0b                      # end

version texte func.wat

version binaire

func.wasm

exemple minimal

(module
  (func $getValue (result i32)
    i32.const 38)
)
(module
  (func $getValue (result i32)
    i32.const 38)
  (export "getValue" (func $getValue))
)

exemple minimal

(module
  (func $getValue (result i32)
    i32.const 38)
  (export "getValue" (func $getValue))
)
const binary = await fetch('./func.wasm')
38

func.wat

func.js

console

const binary = await fetch('./func.wasm')
const module = await WebAssembly.compile(binary)
const binary = await fetch('./func.wasm')
const module = await WebAssembly.compile(binary)
const instance = await WebAssembly.instantiate(module)
const binary = await fetch('./func.wasm')
const module = await WebAssembly.compile(binary)
const instance = await WebAssembly.instantiate(module)
console.log(instance.exports.getValue())

exemple minimal

func.wasm

(module
  (func $add (param $a i32) (param $b i32) (result i32)
    get_local $a
    get_local $b
    i32.add)
  (export "add" (func $add))
)
const binary = await fetch('./add.wasm')
const module = await WebAssembly.compile(binary)
const instance = await WebAssembly.instantiate(module)
console.log(instance.exports.add(38, 4))
42

add.wat

add.js

console

avec paramètres

(module
  (import "global" "log"
    (func $log (param i32))
  )
  (func $mylog (param $value i32)
    (call $log (get_local $value))
  )
  (export "logInteger" (func $mylog))
)
const imports = {
  global: {
    log: console.log
  }
}
const instance = await WebAssembly.instantiate(
  module, imports
)

instance.exports.logInteger(33)
33

console

appel d'une fonction JavaScript depuis WebAssembly

Mémoire de WebAssembly

l'instance lit et écrit dans sa mémoire

instance d'un module WebAssembly

zone mémoire de l'instance

instance d'un module WebAssembly

zone mémoire de l'instance

Mémoire de WebAssembly

JavaScript

mais il est également possible que JavaScript accède à cette mémoire

Mémoire de WebAssembly

la mémoire de l'instance

est vue

comme un BufferArray

par JavaScript

le partage se fait par import ou par export

comme pour les fonctions

(module
  (func $set (param $index i32) (param $value i32)
    (i32.store8
      (get_local $index)
      (get_local $value)
    )
  )
  (export "set" (func $set))
  (memory (export "memory") 1)
)
// ...

const buffer = instance.exports.memory.buffer
Int8Array [ 1, 4, 7 ]
Int8Array [ 1, 8, 7 ]

console

(module
  (func $set (param $index i32) (param $value i32)
    (i32.store8
      (get_local $index)
      (get_local $value)
    )
  )
  (export "set" (func $set))
)
// ...

const buffer = instance.exports.memory.buffer

const array = new Int8Array(buffer, 0, 3)
array.set([1, 4, 7])
console.log(array)

instance.exports.set(1, 8)

console.log(array)
// ...

const buffer = instance.exports.memory.buffer

const array = new Int8Array(buffer, 0, 3)
array.set([1, 4, 7])
// ...

const buffer = instance.exports.memory.buffer

const array = new Int8Array(buffer, 0, 3)
array.set([1, 4, 7])


instance.exports.set(1, 8)

échanges mémoire entre JavaScript et WebAssembly

(module
  (import "global" "memory" (memory 1))
  (func $increment (param $index i32)
    (i32.store8
        (get_local $index)
        (i32.add
            (i32.load8_s (get_local $index))
            (i32.const 1)
        )
    )
  )
  (export "increment" (func $increment))
)
const imports = {
  global: {
    memory: new WebAssembly.Memory({ initial: 1 })
  }
}
Int8Array [ 6, 3, 4 ]
Int8Array [ 6, 3, 5 ]

console

const imports = {
  global: {
    memory: new WebAssembly.Memory({ initial: 1 })
  }
}
const buffer = imports.global.memory.buffer
const array = new Int8Array(buffer, 0, 3)
array.set([6, 3, 4])
const imports = {
  global: {
    memory: new WebAssembly.Memory({ initial: 1 })
  }
}
const buffer = imports.global.memory.buffer
const array = new Int8Array(buffer, 0, 3)
array.set([6, 3, 4])
console.log(array)

const instance = await WebAssembly.instantiate(
  module, imports
)

instance.exports.increment(2)
console.log(array)
const imports = {
  global: {
    memory: new WebAssembly.Memory({ initial: 1 })
  }
}
const buffer = imports.global.memory.buffer
const array = new Int8Array(buffer, 0, 3)
array.set([6, 3, 4])


const instance = await WebAssembly.instantiate(
  module, imports
)
const imports = {
  global: {
    memory: new WebAssembly.Memory({ initial: 1 })
  }
}
const buffer = imports.global.memory.buffer
const array = new Int8Array(buffer, 0, 3)
array.set([6, 3, 4])


const instance = await WebAssembly.instantiate(
  module, imports
)

instance.exports.increment(2)

échanges mémoire entre JavaScript et WebAssembly - alternative

moteur WebAssembly

module

instance

imports et exports

mémoire linéaire

format binaire

Notions

int add(int a, int b) {
    return a + b;
}
const imports = {
  env: {
    memoryBase: 0,
    tableBase: 0,
    memory: new WebAssembly.Memory({initial: 256}),
    table: new WebAssembly.Table(
      {initial: 0, element: 'anyfunc'}
    )
  }
}
const instance = await WebAssembly.instantiate(
  module, imports
)
console.log(instance.exports._add(25, 4))
29

console

$ emcc add.c \
    -Os -s SIDE_MODULE=1 \
    -o add.wasm

add.c

compilation avec emscripten

emscripten et intégration avec JavaScript

#include <stdio.h>
int main() {
  FILE *f = fopen("output.txt", "w");
  fprintf(f, "WebAssembly");
  fclose(f);
}
$ firefox webapp.html
>> Module.FS.readFile('output.txt', {encoding: 'utf8'})
"WebAssembly"
$ emcc main.c \
    -s NO_EXIT_RUNTIME=1 \
    -s 'EXTRA_EXPORTED_RUNTIME_METHODS=["FS"]' \
    -o webapp.html

pseudo système de fichiers dans le navigateur fourni par emscripten

"WebAssembly"
$ wget http://zlib.net/zlib-1.2.11.tar.gz
$ tar xf zlib-1.2.11.tar.gz
$ cd zlib-1.2.11.tar.gz
$ emconfigure ./configure --prefix=./dist
$ emmake make
$ emmake make install

Emscripten pour compiler des outils existants

WebAssembly, pour faire quoi ?

jeux

édition de vidéo ou d'image

application musicale

reconnaissance d'image

VR et AR

visualisation et simulation scientifique

etc.

je prends

je prends

Exemples concrets

d'utilisation de WebAssembly

use case

Magellium - CNES

un outil de visualisation d'images satellite

avec traitements

100% côté navigateur

dans ces outils j'ai manipulé

 

des "petites images" (moins de 100 Mo)

 

dans des formats standards (PNG...)

lisibles par les navigateurs

en général

les données satellites

sont dans d'autres formats

JPEG 2000

très utilisé pour les images satellites

 

images de très grande taille

 

format de compression multiéchelle

 

encodages exotiques

(plus que trois "couleurs", pas de 8bits)

 

 

 

source : wikipedia

$ ll IMG_R2C1.JP2
-rw-r--r-- 1 ada ada 3.3G Nov 12  2009 IMG_R2C1.JP2
$ gdalinfo IMG_R2C1.JP2
  Driver: JP2OpenJPEG/JPEG-2000 driver based on OpenJPEG library
  Files: IMG_R2C1.JP2
  Size is 57344, 28672
  Band 1 Block=4096x4096 Type=UInt16, ColorInterp=Red
    Overviews: 28672x14336, 14336x7168, 7168x3584, 3584x1792, 1792x896
    NBITS=12
  Band 2 Block=4096x4096 Type=UInt16, ColorInterp=Green
    Overviews: 28672x14336, 14336x7168, 7168x3584, 3584x1792, 1792x896
    NBITS=12
  Band 3 Block=4096x4096 Type=UInt16, ColorInterp=Blue
    Overviews: 28672x14336, 14336x7168, 7168x3584, 3584x1792, 1792x896
    NBITS=12
  Band 4 Block=4096x4096 Type=UInt16, ColorInterp=Undefined
    Overviews: 28672x14336, 14336x7168, 7168x3584, 3584x1792, 1792x896
    NBITS=12

mais JPEG 2000 n'est pas un format lisible par les navigateurs

il existe des bibliothèques de lecture en C

OpenJPEG

open source

Kakadu

payant (licence de développement)

la référence pour le JPEG 2000

on utilise emscripten pour compiler Kakadu en WebAssembly

$ kdu_expand \
      -i full_image.jp2 \
      -o extract.bmp \
      -region {0.3,0.05},{0.8,0.02}

Ligne de commande pour

extraire une partie d'une image JPEG 2000 et pour l'enregistrer en BMP

affichage dans le navigateur ?

bibliothèque JavaScript

affichage de

données cartographiques

et d'images

map.addLayer(new ol.layer.Tile({
    source: new ol.source.TileImage({
        tileLoadFunction: function (tile, zxyTileUrl) {
            const [ x, y, w, h, reduce ] = getKduRegion(zxyTileUrl)
            callWasmKduExpand([
                '-i', inputJp2File,
                '-o', subBmpFile,
                '-region', `{${x},${y}},{${w},${h}})`,
                '-reduce', `${reduce}`
            ])
            const binary = readFileFromEmscriptenFS(subBmpFile)
            const base64String = binaryArrayToBase64String(binary)
            const dataUrl = 'data:image/bmp;base64,' + base64String
            tile.getImage().src = dataUrl
        }
    })
}))

ol = OpenLayers

affichage ok et fluide !

(démo dans quelques slides)

mais...

uniquement sur des petites images JP2

(max 100 Mo)

raison :

fichiers stockés dans la mémoire du navigateur

(MEMFS d'emscripten)

comment lire et afficher

des images de plusieurs Go ?

utiliser l'API File de JavaScript

permet d'accéder à un intervalle d'octets si appelé depuis un Web Worker !

emscripten fournit

le pseudo système de fichiers WORKERFS

qui permet cela

wasm

archi pour fichier local

affichage d'un fichier local de

plusieurs Go

ok et fluide !

affichage d'un fichier distant de

plusieurs Go ?

archi pour fichier local

archi pour fichier distant

wasm

wasm

affichage d'un fichier distant de

plusieurs Go

ok et fluide !

WebAssembly

en dehors du navigateur

#include <stdio.h>
int main() {
    printf("WebAssembly\n");
}
$ emcc main.c -o cli.js
$ ls
cli.js  cli.wasm  main.c
$ node cli.js
WebAssembly

détection d'avions avec l'algorithme ICF

traitements en batch de la

détection d'avions avec ICF

sur un ensemble de grandes images

avec le même binaire WebAssembly

mais côté serveur

Google Cloud

Functions

Google Cloud

PubSUb

old

exécution de wasm avec Node.js

moteur JavaScript

+

moteur WebAssembly

?

moteurs wasm

en dehors du navigateur

https://www.fastly.com/blog/announcing-lucet-fastly-native-webassembly-compiler-runtime

#include <stdio.h>
int main() {
    printf("WebAssembly\n");
}
$ emcc main.c -o cli.js
$ ls
cli.js  cli.wasm  main.c
$ node cli.js
WebAssembly
$ ls
cli.js  cli.wasm  main.c
$ wasmer run cli.wasm
WebAssembly

WASI

WebAssembly system interface

https://hacks.mozilla.org/2019/03/standardizing-wasi-a-webassembly-system-interface/

crédit :        @linclark         article mozilla hacks sur WASI

crédit :        @linclark         article mozilla hacks sur WASI

capability-based security

Rust + WebAssembly

Outils

Binding entre JavaScript et Rust

via WebAssembly

wasm-bindgen

Rust + WebAssembly

Outils

Binding entre JavaScript et Rust

via WebAssembly

wasm-bindgen

présentation par Nick Fitzgerald @fitzgen :

Rust + WebAssembly

Outils

"building, testing, and publishing Rust-generated WebAssembly"

wasm-pack

Rust + WebAssembly

Outils

pour une intégration

dans le workflow de build

plugin wasm pour webpack

Rust + WebAssembly

Comment ?

un Rust book de parcours de dev pas à pas

Tutoriel Game of Life

Merci !

Nicolas Decoster      @ogadaki

A WebAssembly tale

By Nicolas Decoster

A WebAssembly tale

Conférence WebAssembly pour Electronic Tales

  • 226