asm.js: tradurre C in JavaScript

3 luglio 2018

Negli ultimi anni abbbiamo assistito ad una progressiva convergenza delle tecnologie impiegate in ambito web e di quelle tradizionalmente legate allo sviluppo di applicazioni a più basso livello.

Se da una parte esistono framework che consentono di sviluppare applicazioni desktop usando esclusivamente tecnologie web (ad esempio Electron), esistono anche progetti rivolti nella direzione opposta: impiegare tecnologie e linguaggi tradizionalmente adottati in ambito desktop per lo sviluppo di applicazioni web.

Una svolta in tal senso è arrivata con l’avvento di asm.js, un progetto sviluppato dalla Mozilla Fundation, nato con lo scopo di definire un linguaggio compatibile con JavaScript e utilizzabile come target di compilazione. asm.js non è in effetti un linguaggio a sé, ma più che altro un sottoinsieme di JavaScript tale da poter garantire piena compatibilità con linguaggi fortemente tipizzati. Pertanto, il codice asm.js può essere eseguito da un qualsiasi interprete JavaScript, sia esso parte integrate di un web browser, o a sé stante (ad esempio Node.js).

Grazie alla specifica di asm.js ed all’esistenza di opportuni compilatori, è possibile compilare codice sorgente scritto in un linguaggio di programmazione “tradizionale” (ad esempio, C, C++, Ruby, Python, ecc.) “traducendolo” in una forma eseguibile da un interprete JavaScript.

Compilare in asm.js

Emscripten è un compilatore basato su LLVM in grado di tradurre codice C/C++ in asm.js. In particolare, Emscripten è un back end per LLVM in grado di tradurre codice in asm.js. Questo consente di mantenere inalterato il front end (nel caso di C/C++ il famoso clang), beneficiando quindi di tutte le ottimizzazioni già operate dallo stesso sul codice.

Ricordiamo, infatti, che il processo di compilazione è essenzialmente diviso in due parti: nella prima, il front end traduce ed ottimizza il codice sorgente in un linguaggio indipendente da quello sorgente e dalla piattaforma target detto “rappresentazione intermedia” (IR, Intermediate Representation). Nella seconda, il back end provvede a tradurre il codice IR in codice target, come ad esempio, codice macchina x86, oppure, come nel caso di Emscripten, codice asm.js.

La particolare architettura di LLVM rende quindi possibile una grande flessibilità, potendo “combinare” qualsiasi front end con qualsiasi back end disponibile. Tra i linguaggi attualmente compilabili in asm.js citiamo (oltre a C e C++) anche Rust, Lua, Perl, Python e Ruby.

Caratteristiche

Come detto in precedenza, asm.js garantisce piena compatibilità con i linguaggi fortemente tipizzati. Ciò significa che, a differenza di JavaScript, è necessario assicurarsi che ogni espressione sia valutata utilizzando i tipi definiti a tempo di compilazione. asm.js si avvale di alcune operazioni aggiuntive per “forzare” il processo di inferenza del tipo operato dall’interprete JavaScript.

Ad esempio, il codice in linguaggio C:

int f(int i) {
  return i + 1;
}

viene tradotto in asm.js in:

function f(i) {
  i = i|0;
  return (i + 1)|0;
}

Come si può facilmente notare, le espressioni che lavorano su tipi interi sono tradotte aggiungendo in coda l’istruzione “| 0” (OR bitwise con zero). Bisogna ricordare infatti che in JavaScript le variabili numeriche sono per default floating point. L’aggiunta dell’istruzione | 0 garantisce che l’interprete JavaScript calcoli l’espressione usando variabili intere, poiché in JavaScript gli operatori bitwise convertono i loro operandi in interi automaticamente.

Sebbene possa apparire come una forzatura, si tratta in realtà di un elegante espediente, che consente di superare le “limitazioni” di JavaScript pur mantenendo il codice perfettamente compatibile con gli interpreti esistenti.

Applicazioni

Le potenzialità offerte da una simile tecnologia sono innumerevoli: in primo luogo, essa ha reso possibile il porting di librerie ed applicazioni esistenti semplicemente ricompilandole. La ricompilazione è un processo automatico, ripetibile, e decisamente meno oneroso di una riscrittura. Ciò ha portato alla nascita di alcuni interessanti progetti con lo scopo di portare applicazioni desktop sul browser.

Tra gli esempi illustri citiamo i game engine Unity3D e Unreal Engine, l’emulatore DOSbox, il software AutoCAD nella sua versione web, il framework Qt e molto altro.

I vantaggi offerti da questo processo sono molteplici. In primo luogo, si pensi alla portabilità: un’applicazione compilata in asm.js potrà girare su ogni browser che supporti JavaScript, indipendentemente dal fatto che giri su Windows, Linux, macOS, iOS, Android, ecc. senza che essa debba essere modificata o semplicemente ricompilata per le varie piattaforme. Inoltre, si beneficia di una maggiore sicurezza, poiché le applicazioni girano nel contesto della sandbox di un web browser.

In altre parole, il livello di astrazione offerto dal browser è altissimo e sono molteplici gli scenari in cui questo è un indubbio vantaggio.

Performance

Purtroppo, non è tutto oro quel che luccica. Come è intuibile, vi è un certo overhead introdotto dall’interprete JavaScript. Sebbene esistano interpreti JavaScript molto efficienti, l’overhead introdotto da asm.js è di circa 1.5 / 2 volte. Ciò significa che un’applicazione compilata in asm.js sarà da una volta e mezza a due volte più lenta della stessa compilata in modo nativo.

Oltre asm.js: WebAssembly

Per superare le limitazioni di asm.js è nato un nuovo standard, WebAssembly (in breve, Wasm), che a differenza di asm.js, non è basato su JavaScript ma è a tutti gli effetti un formato bytecode. Esso pertanto richiede un supporto specifico dei browser per poter essere eseguito. Ad oggi, la maggior parte dei browser moderni supporta Wasm nativamente, sia su desktop che su mobile, ed il compilatore Emscripten è stato aggiornato per supportare Wasm come alternativa a asm.js.

I vantaggi di Wasm rispetto ad asm.js sono facilmente intuibili. In primo luogo, è nettamente più veloce, poiché la specifica del linguaggio non è vincolata alle caratteristiche di JavaScript. In particolare, Wasm supporta interi a 64 bit, istruzioni load and store, SIMD e molto altro.

Ci si potrebbe chiedere perché si sia creata una nuova specifica invece di utilizzarne una esistente, come ad esempio la già citata IR di LLVM. La ragione di questa scelta va ricercata nelle esigenze che hanno portato alla nascita di Wasm:

  • portabilità, ovvero indipendenza dalla piattaforma hardware e software sottostante;
  • stabilità: la specifica non dovrebbe cambiare rapidamente nel tempo, poiché questo creerebbe incompatibilità con le versioni precedenti dei browser creando frammentazione;
  • dimensione della codifica: il codice Wasm va trasmesso su Internet e pertanto deve essere compatto il più possibile;
  • velocità di decodifica e compilazione: la compilazione di Wasm in codice nativo è a carico del browser. Per limitare i tempi di avvio delle applicazioni, questa fase deve essere rapida;

Ad esempio, nello sviluppo di un compilatore, e quindi della sua IR, ci si concentra più sulla qualità del codice prodotto piuttosto che sui tempi di compilazione dato che questa operazione viene effettuata solo dagli sviluppatori. Nel caso di Wasm invece, la compilazione è parte integrante della fase di “caricamento” dell’applicazione. Ecco perché, le IR esistenti mal si prestano agli usi per cui è stata concepita Wasm.

WebAssembly aspira dunque a diventare una tecnologia standard e multipiattaforma per lo sviluppo di applicazioni lato client ad alte performance, garantendo al contempo alti livelli di sicurezza (ad esempio, grazie all’uso del sandboxing). Come indicato dagli stessi autori della specifica, WebAssembly non sostituirà JavaScript, ma lo affiancherà in tutte quelle circostanze in cui si richiedono maggiori performance o l’integrazione con librerie e tecnologie sviluppate con altri linguaggi.

Se vuoi aggiornamenti su asm.js: tradurre C in JavaScript inserisci la tua e-mail nel box qui sotto:
 
X
Se vuoi aggiornamenti su asm.js: tradurre C in JavaScript

inserisci la tua e-mail nel box qui sotto:

Ho letto e acconsento l'informativa sulla privacy

Acconsento al trattamento dei dati per attività di marketing