Rilevamento delle funzionalità di WebAssembly

Scopri come utilizzare le ultime funzionalità di WebAssembly supportando al contempo gli utenti in tutti i browser.

Ingvar Stepanyan
Ingvar Stepanyan

WebAssembly 1.0 è stato rilasciato quattro anni fa, ma lo sviluppo non si è fermato qui. Le nuove funzionalità vengono aggiunte tramite il processo di standardizzazione della proposta. Come avviene generalmente per le nuove funzionalità sul web, l'ordine e le tempistiche di implementazione possono variare significativamente tra i diversi motori. Se vuoi utilizzare queste nuove funzionalità, devi assicurarti che nessuno dei tuoi utenti venga escluso. Questo articolo spiega un approccio per raggiungere questo obiettivo.

Alcune nuove funzionalità migliorano le dimensioni del codice aggiungendo nuove istruzioni per le operazioni comuni, altre aggiungono potenti primitive di prestazioni e altre migliorano l'esperienza degli sviluppatori e l'integrazione con il resto del web.

Puoi trovare l'elenco completo delle proposte e le rispettive fasi nel repository ufficiale oppure monitorarne lo stato di implementazione nei motori sulla pagina ufficiale della roadmap delle funzionalità.

Per assicurarti che gli utenti di tutti i browser possano utilizzare la tua applicazione, devi capire quali funzioni desideri utilizzare. Quindi, li suddividili in gruppi in base al supporto del browser. Quindi, compila il codebase separatamente per ciascuno di questi gruppi. Infine, lato browser devi rilevare le funzionalità supportate e caricare il bundle JavaScript e Wasm corrispondente.

Scelta e raggruppamento delle caratteristiche

Esaminiamo questi passaggi scegliendo come esempio un insieme di caratteristiche arbitrarie. Supponiamo di voler utilizzare SIMD, thread e gestione delle eccezioni nella mia libreria per motivi di dimensioni e prestazioni. Il supporto del browser è il seguente:

Una tabella che mostra il supporto browser delle funzionalità scelte.
. Visualizza questa tabella delle funzionalità su webassembly.org/roadmap.

Puoi suddividere i browser nelle seguenti coorti per assicurarti che ogni utente usufruisca al meglio dell'esperienza ottimizzata:

  • Browser basati su Chrome: sono supportati thread, SIMD e gestione delle eccezioni.
  • Firefox: le reti Thread e SIMD sono supportate, ma non la gestione delle eccezioni.
  • Safari: i thread sono supportati, mentre SIMD e gestione delle eccezioni non lo sono.
  • Altri browser: presupponi solo il supporto WebAssembly di base.

Questa suddivisione è suddivisa per supporto delle funzionalità nell'ultima versione di ciascun browser. I browser moderni sono sempre attuali e si aggiornano automaticamente, quindi nella maggior parte dei casi devi preoccuparti solo dell'ultima release. Tuttavia, se includi WebAssembly di base come coorte di riserva, puoi comunque fornire un'applicazione funzionante anche per gli utenti con browser obsoleti.

Compilazione per diversi insiemi di caratteristiche

WebAssembly non dispone di un modo integrato per rilevare le funzionalità supportate in runtime, pertanto tutte le istruzioni del modulo devono essere supportate nella destinazione. Per questo motivo, devi compilare il codice sorgente in Wasm separatamente per ognuno di questi diversi insiemi di caratteristiche.

Ogni toolchain e sistema di compilazione è diverso e dovrai consultare la documentazione del tuo compilatore per sapere come modificare queste funzionalità. Per semplicità, nell'esempio seguente userò una libreria C++ a un singolo file e mostro come compilarla con Emscripten.

Userò SIMD tramite l'emulazione SSE2, i thread tramite il supporto della libreria Pthreads e sceglierò tra la gestione delle eccezioni Wasm e l'implementazione di JavaScript di riserva:

# First bundle: threads + SIMD + Wasm exceptions
$ emcc main.cpp -o main.threads-simd-exceptions.mjs -pthread -msimd128 -msse2 -fwasm-exceptions
# Second bundle: threads + SIMD + JS exceptions fallback
$ emcc main.cpp -o main.threads-simd.mjs -pthread -msimd128 -msse2 -fexceptions
# Third bundle: threads + JS exception fallback
$ emcc main.cpp -o main.threads.mjs -pthread -fexceptions
# Fourth bundle: basic Wasm with JS exceptions fallback
$ emcc main.cpp -o main.basic.mjs -fexceptions

Il codice C++ stesso può utilizzare #ifdef __EMSCRIPTEN_PTHREADS__ e #ifdef __SSE2__ per scegliere in modo condizionale tra le implementazioni parallele (thread e SIMD) delle stesse funzioni e le implementazioni seriali al momento della compilazione. L'URL avrà il seguente aspetto:

void process_data(std::vector<int>& some_input) {
#ifdef __EMSCRIPTEN_PTHREADS__
#ifdef __SSE2__
  // …implementation using threads and SIMD for max speed
#else
  // …implementation using threads but not SIMD
#endif
#else
  // …fallback implementation for browsers without those features
#endif
}

La gestione delle eccezioni non richiede istruzioni #ifdef, perché può essere utilizzata allo stesso modo da C++, a prescindere dall'implementazione sottostante scelta tramite i flag di compilazione.

Caricamento del bundle corretto in corso...

Una volta creati i bundle per tutte le coorti di caratteristiche, devi caricare quello corretto dall'applicazione JavaScript principale. A tale scopo, devi prima individuare le funzionalità supportate nel browser corrente. Puoi farlo con la libreria wasm-feature-detect. Combinandolo con l'importazione dinamica, puoi caricare il bundle più ottimizzato in qualsiasi browser:

import { simd, threads, exceptions } from 'https://unpkg.com/wasm-feature-detect?module';

let initModule;
if (await threads()) {
  if (await simd()) {
    if (await exceptions()) {
      initModule = import('./main.threads-simd-exceptions.mjs');
    } else {
      initModule = import('./main.threads-simd.mjs');
    }
  } else {
    initModule = import('./main.threads.mjs');
  }
} else {
  initModule = import('./main.basic.mjs');
}

const Module = await initModule();
// now you can use `Module` Emscripten object like you normally would

Parole finali

In questo post, ho mostrato come scegliere, creare e passare da un bundle all'altro per diversi set di funzionalità.

Con l'aumento del numero di caratteristiche,il numero di coorti di caratteristiche potrebbe diventare non gestibile. Per ovviare a questo problema, puoi scegliere coorti di caratteristiche in base ai tuoi dati utente reali, ignorare i browser meno popolari e consentire loro di tornare a coorti leggermente meno ottimali. Finché l'applicazione continua a funzionare per tutti gli utenti, questo approccio può fornire un ragionevole equilibrio tra miglioramento progressivo e prestazioni di runtime.

In futuro, WebAssembly potrebbe avere un modo integrato per rilevare le funzionalità supportate e passare tra diverse implementazioni della stessa funzione all'interno del modulo. Tuttavia, un meccanismo di questo tipo sarebbe a sua volta una funzionalità post-MVP che dovrai rilevare e caricare in modo condizionale utilizzando l'approccio sopra indicato. Fino ad allora, questo approccio rimane l'unico modo per creare e caricare il codice utilizzando le nuove funzionalità di WebAssembly in tutti i browser.