From 58d3a9aeebde3228e3af60f5b356ae3c0e7f4222 Mon Sep 17 00:00:00 2001 From: Joshua Chen Date: Fri, 7 Jun 2024 12:46:31 +0800 Subject: [PATCH] Use DataView to access WebAssembly memory (#33960) * Use DataView to access WebAssembly memory * Apply suggestions from code review Co-authored-by: Hamish Willee * Update files/en-us/webassembly/javascript_interface/memory/memory/index.md Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --------- Co-authored-by: Hamish Willee Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- .../reference/global_objects/dataview/index.md | 4 +++- .../javascript_interface/memory/buffer/index.md | 4 ++-- .../javascript_interface/memory/index.md | 12 +++++++----- .../javascript_interface/memory/memory/index.md | 4 ++-- .../webassembly/loading_and_running/index.md | 2 +- .../webassembly/using_the_javascript_api/index.md | 15 +++++++-------- 6 files changed, 22 insertions(+), 19 deletions(-) diff --git a/files/en-us/web/javascript/reference/global_objects/dataview/index.md b/files/en-us/web/javascript/reference/global_objects/dataview/index.md index 706bd2a4b1a66ef..3882b9ec7c40df1 100644 --- a/files/en-us/web/javascript/reference/global_objects/dataview/index.md +++ b/files/en-us/web/javascript/reference/global_objects/dataview/index.md @@ -13,7 +13,7 @@ The **`DataView`** view provides a low-level interface for reading and writing m ### Endianness -Multi-byte number formats are represented in memory differently depending on machine architecture — see [Endianness](/en-US/docs/Glossary/Endianness) for an explanation. `DataView` accessors provide explicit control of how data is accessed, regardless of the executing computer's endianness. +Multi-byte number formats are represented in memory differently depending on machine architecture — see [Endianness](/en-US/docs/Glossary/Endianness) for an explanation. `DataView` accessors provide explicit control of how data is accessed, regardless of the executing computer's endianness. For example, [WebAssembly](/en-US/docs/WebAssembly) memory is always little-endian, so you should use `DataView` instead of typed arrays to read and write multi-byte values. See [`WebAssembly.Memory`](/en-US/docs/WebAssembly/JavaScript_interface/Memory) for an example. ```js const littleEndian = (() => { @@ -25,6 +25,8 @@ const littleEndian = (() => { console.log(littleEndian); // true or false ``` +> **Note:** `DataView` defaults to big-endian read and write, but most platforms use little-endian. + ### 64-bit Integer Values Some browsers don't have support for {{jsxref("DataView.prototype.setBigInt64()")}} and {{jsxref("DataView.prototype.setBigUint64()")}}. So to enable 64-bit operations in your code that will work across browsers, you could implement your own `getUint64()` function, to obtain values with precision up to {{jsxref("Number.MAX_SAFE_INTEGER")}} — which could suffice for certain cases. diff --git a/files/en-us/webassembly/javascript_interface/memory/buffer/index.md b/files/en-us/webassembly/javascript_interface/memory/buffer/index.md index 28a76a340a6b5ca..18da1ef4db0c371 100644 --- a/files/en-us/webassembly/javascript_interface/memory/buffer/index.md +++ b/files/en-us/webassembly/javascript_interface/memory/buffer/index.md @@ -24,9 +24,9 @@ const memory = new WebAssembly.Memory({ WebAssembly.instantiateStreaming(fetch("memory.wasm"), { js: { mem: memory }, }).then((obj) => { - const summands = new Uint32Array(memory.buffer); + const summands = new DataView(memory.buffer); for (let i = 0; i < 10; i++) { - summands[i] = i; + summands.setUint32(i * 4, i, true); // WebAssembly is little endian } const sum = obj.instance.exports.accumulate(0, 10); console.log(sum); diff --git a/files/en-us/webassembly/javascript_interface/memory/index.md b/files/en-us/webassembly/javascript_interface/memory/index.md index 701e6246f4966b5..2e5ca1c2a53943a 100644 --- a/files/en-us/webassembly/javascript_interface/memory/index.md +++ b/files/en-us/webassembly/javascript_interface/memory/index.md @@ -18,6 +18,8 @@ Originally you could only perform memory operations on a single memory in the Wa More recent implementations allow WebAssembly [memory instructions](/en-US/docs/WebAssembly/Reference/Memory) to operate on a specified memory. For more information see [Multiple memories](/en-US/docs/WebAssembly/Understanding_the_text_format#multiple_memories) in _Understanding WebAssembly text format_. +> **Note:** WebAssembly memory is always in little-endian format, regardless of the platform it's run on. Therefore, for portability, you should read and write multi-byte values in JavaScript using {{jsxref("DataView")}}. + ## Constructor - [`WebAssembly.Memory()`](/en-US/docs/WebAssembly/JavaScript_interface/Memory/Memory) @@ -46,7 +48,7 @@ const memory = new WebAssembly.Memory({ }); ``` -The following example (see [memory.html](https://github.com/mdn/webassembly-examples/blob/main/js-api-examples/memory.html) on GitHub, and [view it live also](https://mdn.github.io/webassembly-examples/js-api-examples/memory.html)) fetches and instantiates the loaded "memory.wasm" bytecode using the [`WebAssembly.instantiateStreaming()`](/en-US/docs/WebAssembly/JavaScript_interface/instantiateStreaming_static) function, while importing the memory created in the line above. It then stores some values in that memory, exports a function, and uses the exported function to sum those values. +The following example (see [memory.html](https://github.com/mdn/webassembly-examples/blob/main/js-api-examples/memory.html) on GitHub, and [view it live also](https://mdn.github.io/webassembly-examples/js-api-examples/memory.html)) fetches and instantiates the loaded "memory.wasm" bytecode using the [`WebAssembly.instantiateStreaming()`](/en-US/docs/WebAssembly/JavaScript_interface/instantiateStreaming_static) function, while importing the memory created in the line above. It then stores some values in that memory, exports a function, and uses the exported function to sum those values. Note the use of {{jsxref("DataView")}} to access the memory so we always use little-endian format. ```js const memory = new WebAssembly.Memory({ @@ -57,9 +59,9 @@ const memory = new WebAssembly.Memory({ WebAssembly.instantiateStreaming(fetch("memory.wasm"), { js: { mem: memory }, }).then((obj) => { - const summands = new Uint32Array(memory.buffer); + const summands = new DataView(memory.buffer); for (let i = 0; i < 10; i++) { - summands[i] = i; + summands.setUint32(i * 4, i, true); // WebAssembly is little endian } const sum = obj.instance.exports.accumulate(0, 10); console.log(sum); @@ -70,8 +72,8 @@ Another way to get a `WebAssembly.Memory` object is to have it exported by a Web ```js WebAssembly.instantiateStreaming(fetch("memory.wasm")).then((obj) => { - const values = new Uint32Array(obj.instance.exports.memory.buffer); - console.log(values[0]); + const values = new DataView(obj.instance.exports.memory.buffer); + console.log(values.getUint32(0, true)); }); ``` diff --git a/files/en-us/webassembly/javascript_interface/memory/memory/index.md b/files/en-us/webassembly/javascript_interface/memory/memory/index.md index 0c1d09e74a788ac..645769b41123b8d 100644 --- a/files/en-us/webassembly/javascript_interface/memory/memory/index.md +++ b/files/en-us/webassembly/javascript_interface/memory/memory/index.md @@ -69,9 +69,9 @@ const memory = new WebAssembly.Memory({ WebAssembly.instantiateStreaming(fetch("memory.wasm"), { js: { mem: memory }, }).then((obj) => { - const summands = new Uint32Array(memory.buffer); + const summands = new DataView(memory.buffer); for (let i = 0; i < 10; i++) { - summands[i] = i; + summands.setUint32(i * 4, i, true); // WebAssembly is little endian } const sum = obj.instance.exports.accumulate(0, 10); console.log(sum); diff --git a/files/en-us/webassembly/loading_and_running/index.md b/files/en-us/webassembly/loading_and_running/index.md index 283ae277f21e35d..b5c6741663db845 100644 --- a/files/en-us/webassembly/loading_and_running/index.md +++ b/files/en-us/webassembly/loading_and_running/index.md @@ -69,7 +69,7 @@ WebAssembly.instantiateStreaming(fetch("myModule.wasm"), importObject).then( obj.instance.exports.exported_func(); // or access the buffer contents of an exported memory: - const i32 = new Uint32Array(obj.instance.exports.memory.buffer); + const dv = new DataView(obj.instance.exports.memory.buffer); // or access the elements of an exported table: const table = obj.instance.exports.table; diff --git a/files/en-us/webassembly/using_the_javascript_api/index.md b/files/en-us/webassembly/using_the_javascript_api/index.md index c49060b6e739d40..398a48729a1b0e1 100644 --- a/files/en-us/webassembly/using_the_javascript_api/index.md +++ b/files/en-us/webassembly/using_the_javascript_api/index.md @@ -103,13 +103,14 @@ Let's start exploring this by looking at a quick example. WebAssembly memory exposes its bytes by providing a buffer getter/setter that returns an ArrayBuffer. For example, to write 42 directly into the first word of linear memory, you can do this: ```js - new Uint32Array(memory.buffer)[0] = 42; + const data = new DataView(memory.buffer); + data.setUint32(0, 42, true); ``` - You can then return the same value using: + Note the use of `true`, which enforces little-endian read and write, since WebAssembly memory is always little-endian. You can then return the same value using: ```js - new Uint32Array(memory.buffer)[0]; + data.getUint32(0, true); ``` 3. Try this now in your demo — save what you've added so far, load it in your browser, then try entering the above two lines in your JavaScript console. @@ -149,17 +150,15 @@ Let's make the above assertions clearer by looking at a more involved memory exa 3. Since this module exports its memory, given an Instance of this module called instance we can use an exported function `accumulate()` to create and populate an input array directly in the module instance's linear memory (`mem`). Add the following into your code, where indicated: ```js - const i32 = new Uint32Array(memory.buffer); - + const summands = new DataView(memory.buffer); for (let i = 0; i < 10; i++) { - i32[i] = i; + summands.setUint32(i * 4, i, true); } - const sum = results.instance.exports.accumulate(0, 10); console.log(sum); ``` -Note how we create the {{jsxref("Uint32Array")}} view on the Memory object's buffer ([`Memory.prototype.buffer`](/en-US/docs/WebAssembly/JavaScript_interface/Memory/buffer)), not on the Memory itself. +Note how we create the {{jsxref("DataView")}} view on the Memory object's buffer ([`Memory.prototype.buffer`](/en-US/docs/WebAssembly/JavaScript_interface/Memory/buffer)), not on the Memory itself. Memory imports work just like function imports, only Memory objects are passed as values instead of JavaScript functions. Memory imports are useful for two reasons: