Handling Endianness When Reading Wasm Memory
Reading raw bytes directly from WebAssembly (Wasm) linear memory
requires careful attention to byte order, as WebAssembly standardizes on
a little-endian format. This article explains how to correctly handle
endianness when accessing Wasm memory from a JavaScript host
environment, demonstrating how to use DataView and
TypedArrays to ensure accurate data retrieval regardless of the host
system’s architecture.
WebAssembly and Little-Endian Format
WebAssembly defines its linear memory as a flat array of bytes in little-endian byte order. This means that for multi-byte data types (like 16-bit, 32-bit, or 64-bit integers and floats), the least significant byte is stored at the lowest memory address, and the most significant byte is stored at the highest.
When you read these bytes from the host environment (such as JavaScript in a browser or Node.js), you must ensure your read operations interpret this little-endian layout correctly.
The Safest Solution: Using
DataView
The most robust and CPU-independent way to read WebAssembly memory is
by using JavaScript’s DataView object. Unlike TypedArrays,
DataView allows you to explicitly specify the endianness of
the read operation using a boolean flag.
Step-by-Step Implementation
- Access the Wasm Memory Buffer: Retrieve the
underlying
ArrayBufferfrom your WebAssembly instance. - Create a
DataView: Instantiate aDataViewpointing to that buffer. - Read with Explicit Endianness: Call the getter
methods (like
getUint32orgetFloat32) and passtrueas the second argument to enforce little-endian parsing.
Code Example
// Assume 'wasmInstance' is your instantiated WebAssembly module
const memoryBuffer = wasmInstance.exports.memory.buffer;
// Create a DataView over the entire Wasm memory buffer
const view = new DataView(memoryBuffer);
const byteOffset = 1024; // The memory address you want to read from
// Read a 32-bit unsigned integer (4 bytes) using little-endian order
// The second argument 'true' specifies little-endian
const value32 = view.getUint32(byteOffset, true);
// Read a 64-bit float (8 bytes) using little-endian order
const floatValue64 = view.getFloat64(byteOffset, true);Passing true as the second argument forces the
JavaScript engine to decode the bytes as little-endian, ensuring correct
results on both little-endian and big-endian host platforms.
The Alternative: Using TypedArrays
TypedArrays (like Uint32Array or
Float64Array) are alternative ways to read Wasm memory.
However, TypedArrays always use the native endianness of the
host platform’s CPU.
Because almost all modern consumer hardware (x86_64, ARM) is little-endian, using TypedArrays directly to read WebAssembly memory usually works without issues:
// Creates a view where elements are read using the platform's native endianness
const uint32Array = new Uint32Array(memoryBuffer);
const value = uint32Array[index]; Risks of the TypedArray Approach
While faster than DataView, relying on TypedArrays is
theoretically non-portable. If your JavaScript code runs on a rare
big-endian system, the TypedArray will read the little-endian
WebAssembly memory incorrectly, resulting in scrambled data.
For maximum compatibility and safety, use DataView with
the little-endian flag set to true. If performance is
critical and you must use TypedArrays, you should first detect the host
platform’s endianness at runtime to determine if byte-swapping is
necessary.