00
:
00
:
00
:
00
Corso SEO AI - Usa SEOEMAIL al checkout per il 30% di sconto

Come Funziona WebAssembly

Architettura di WebAssembly

WebAssembly è una stack machine: le istruzioni leggono e scrivono valori da uno stack implicito anziché usare registri nominati come le CPU tradizionali.

Stack Machine

Ogni istruzione consuma operandi dalla cima dello stack e vi deposita il risultato.

;; Calcola (10 + 20) * 3
i32.const 10   ;; stack: [10]
i32.const 20   ;; stack: [10, 20]
i32.add        ;; stack: [30]
i32.const 3    ;; stack: [30, 3]
i32.mul        ;; stack: [90]

Moduli

L’unità fondamentale di Wasm è il modulo. Un modulo può contenere:

  • Funzioni (func): il codice eseguibile.
  • Memoria lineare (memory): un array di byte contiguo.
  • Table (table): un array di riferimenti a funzioni (usato per i puntatori a funzione e il dynamic dispatch).
  • Globali (global): variabili globali mutabili o immutabili.
  • Import/Export: interfaccia con il mondo esterno (JavaScript, host).
(module
  ;; Importa una funzione dall'host
  (import "env" "log" (func $log (param i32)))

  ;; Dichiara 1 pagina di memoria (64 KB)
  (memory (export "mem") 1)

  ;; Tabella di funzioni con 2 slot
  (table 2 funcref)

  ;; Variabile globale mutabile
  (global $counter (mut i32) (i32.const 0))

  ;; Funzione esportata
  (func (export "increment") (result i32)
    global.get $counter
    i32.const 1
    i32.add
    global.set $counter
    global.get $counter)
)

Memoria Lineare

La memoria di Wasm è un singolo blocco contiguo di byte (linear memory), accessibile tramite indici interi. Cresce in pagine da 64 KB. JavaScript e Wasm condividono la stessa vista sulla memoria tramite ArrayBuffer.

// Creare memoria condivisa
const memory = new WebAssembly.Memory({ initial: 1, maximum: 10 });
const view = new Uint8Array(memory.buffer);
view[0] = 42; // Scrivere un byte che Wasm può leggere

Il Ciclo di Vita

Il percorso dal codice sorgente all’esecuzione segue queste fasi:

  1. Codice sorgente: scrivi in C, Rust, Go, AssemblyScript, ecc.
  2. Compilazione: il compilatore produce un file .wasm (bytecode binario).
  3. Fetch: il browser scarica il file .wasm via rete.
  4. Compilazione JIT: il motore del browser compila il bytecode in codice macchina nativo.
  5. Istanziazione: viene creata un’istanza del modulo con le import risolte.
  6. Esecuzione: le funzioni esportate sono chiamabili da JavaScript.
// Ciclo completo in JavaScript
async function loadWasm() {
  // 1. Fetch del modulo
  const response = await fetch("calculator.wasm");

  // 2. Compilazione + istanziazione in un solo passo
  const { instance } = await WebAssembly.instantiateStreaming(response, {
    env: { log: (val) => console.log("Wasm dice:", val) },
  });

  // 3. Esecuzione
  const result = instance.exports.add(5, 3);
  console.log(result); // 8
}

Differenze Chiave con JavaScript

Aspetto JavaScript WebAssembly
Tipizzazione Dinamica Statica (i32, i64, f32, f64)
Parsing Testo → AST → bytecode Binario, decodifica molto veloce
Memoria Garbage Collected Manuale (linear memory)
DOM Accesso diretto Solo tramite bridge JS
Startup Più lento (parsing + compilazione) Più veloce (formato compatto)
Ottimizzazione JIT con deoptimizzazione possibile Ahead-of-time, prestazioni prevedibili

Istruzioni Principali

Le istruzioni Wasm si dividono in categorie:

;; Aritmetica
i32.add    i32.sub    i32.mul    i32.div_s

;; Confronto
i32.eq     i32.lt_s   i32.gt_u   i32.eqz

;; Memoria
i32.load   i32.store  memory.grow memory.size

;; Controllo di flusso
block      loop       br         br_if      if/else

;; Conversione
i32.wrap_i64   i64.extend_i32_s   f32.convert_i32_s

Il flusso di controllo usa blocchi strutturati (niente goto), rendendo Wasm verificabile staticamente e sicuro per l’esecuzione in sandbox.