Memoria e Tipi
Memoria Lineare
La memoria di WebAssembly è un blocco contiguo di byte (linear memory) che può essere letto e scritto sia da Wasm che da JavaScript. Non esiste un heap strutturato o garbage collector nel modello base di Wasm.
WebAssembly.Memory
// Creare memoria: 1 pagina iniziale (64 KB), massimo 10 pagine (640 KB)
const memory = new WebAssembly.Memory({ initial: 1, maximum: 10 });
// Passarla come import al modulo
const importObject = {
env: { memory },
};
// Accedere al buffer
const buffer = new Uint8Array(memory.buffer);
buffer[0] = 255;
In WAT, la memoria si dichiara internamente o si importa:
(module
;; Memoria interna: 1 pagina iniziale
(memory 1)
;; Funzione che scrive il valore 42 all'offset 0
(func (export "write42")
i32.const 0 ;; offset
i32.const 42 ;; valore
i32.store) ;; scrive 4 byte (i32) all'offset 0
;; Funzione che legge dall'offset 0
(func (export "read") (result i32)
i32.const 0
i32.load)
)
Crescita della Memoria
La memoria può crescere a runtime, ma non può mai ridursi.
const memory = new WebAssembly.Memory({ initial: 1, maximum: 100 });
console.log(memory.buffer.byteLength); // 65536 (64 KB)
memory.grow(2); // Aggiunge 2 pagine (128 KB)
console.log(memory.buffer.byteLength); // 196608 (192 KB)
Attenzione: dopo
memory.grow(), il vecchioArrayBufferviene detached. Devi riacquisire il riferimento conmemory.buffer.
let view = new Uint8Array(memory.buffer);
memory.grow(1);
// view è ora INVALIDO
view = new Uint8Array(memory.buffer); // riacquisire
In WAT, si usa l’istruzione memory.grow:
(func (export "growBy") (param $pages i32) (result i32)
local.get $pages
memory.grow) ;; Restituisce la dimensione precedente, o -1 se fallisce
SharedArrayBuffer e Threading
Per Wasm multi-thread, si usa SharedArrayBuffer:
const sharedMemory = new WebAssembly.Memory({
initial: 1,
maximum: 10,
shared: true, // Richiede COOP/COEP headers nel server
});
// Il buffer è un SharedArrayBuffer
const view = new Int32Array(sharedMemory.buffer);
// Operazioni atomiche
Atomics.store(view, 0, 100);
Atomics.load(view, 0); // 100
Atomics.add(view, 0, 5); // atomically aggiunge 5
Tipi Numerici di Wasm
WebAssembly ha solo quattro tipi numerici:
| Tipo | Descrizione | Dimensione |
|---|---|---|
i32 |
Intero 32-bit | 4 byte |
i64 |
Intero 64-bit | 8 byte |
f32 |
Float IEEE 754 singola precisione | 4 byte |
f64 |
Float IEEE 754 doppia precisione | 8 byte |
Non esistono stringhe, booleani, array o oggetti nativi. Tutto deve essere codificato tramite questi tipi base e la memoria lineare.
(func (export "tipi") (param $a i32) (param $b i64) (param $c f32) (param $d f64)
;; i32: usato per interi, booleani, puntatori a memoria
;; i64: interi grandi (attenzione: JS usa BigInt per i64)
;; f32: float a singola precisione
;; f64: equivalente a Number di JS
nop)
Passare Stringhe tra JS e Wasm
Wasm non ha un tipo stringa nativo. Le stringhe vanno scritte in memoria e passate come puntatore + lunghezza.
// Scrivere una stringa nella memoria Wasm
function writeString(memory, string, offset) {
const encoder = new TextEncoder();
const bytes = encoder.encode(string);
const view = new Uint8Array(memory.buffer);
view.set(bytes, offset);
return bytes.length;
}
// Leggere una stringa dalla memoria Wasm
function readString(memory, offset, length) {
const decoder = new TextDecoder();
const view = new Uint8Array(memory.buffer, offset, length);
return decoder.decode(view);
}
// Uso con un modulo
const { instance } = await WebAssembly.instantiateStreaming(
fetch("string_processor.wasm"),
{ env: { memory } }
);
const len = writeString(memory, "Ciao Wasm!", 0);
instance.exports.processString(0, len); // passa puntatore e lunghezza
const result = readString(memory, 256, instance.exports.getResultLen());
TypedArrays come Vista sulla Memoria
I TypedArray di JavaScript offrono viste tipizzate sullo stesso buffer di memoria:
const memory = instance.exports.memory;
// Diverse viste sullo stesso buffer
const bytes = new Uint8Array(memory.buffer); // byte singoli
const ints = new Int32Array(memory.buffer); // interi 32-bit
const floats = new Float64Array(memory.buffer); // float 64-bit
// Scrivere un float64 all'offset 0
floats[0] = 3.14159;
// Lo stesso dato letto come byte
console.log(bytes[0], bytes[1], bytes[2], bytes[3]); // byte grezzi del float
// Attenzione all'allineamento!
// Int32Array[i] corrisponde a byte offset i*4
ints[0] = 1000;
console.log(bytes[0]); // 232 (byte meno significativo di 1000)
L’allineamento è fondamentale: un i32.load all’offset 3 (non multiplo di 4) funziona ma può essere più lento su alcune piattaforme.