-
Notifications
You must be signed in to change notification settings - Fork 22.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Document wasm JS builtins #37201
base: main
Are you sure you want to change the base?
Document wasm JS builtins #37201
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,149 @@ | ||
--- | ||
title: WebAssembly JavaScript builtins | ||
slug: WebAssembly/JavaScript_builtins | ||
page-type: guide | ||
--- | ||
|
||
{{WebAssemblySidebar}} | ||
|
||
WebAssembly JavaScript builtins are wasm equivalents of JavaScript operations that provide a way to use JavaScript features inside wasm modules without having to import JavaScript glue code to provide a bridge between JavaScript and WebAssembly values and calling conventions. | ||
|
||
This provides performance improvements — importing glue code for primitives such as {{jsxref("String")}} can come with a significant overhead. WebAssembly and most languages that target it expect a tight sequence of inline operations rather than an indirect function call, which is how regular imported functions work. | ||
|
||
This article explains how builtins work and which ones are available, then provides a usage example. | ||
|
||
## Problems with importing JavaScript functions | ||
|
||
There are several problems with importing functions from JavaScript to WebAssembly modules: | ||
|
||
- Existing APIs require a conversion to handle differences around the [`this`](/en-US/docs/Web/JavaScript/Reference/Operators/this) value, which WebAssembly function `import` calls leave as `undefined`. | ||
- Certain primitives use JavaScript operators such as [`===`](/en-US/docs/Web/JavaScript/Reference/Operators/Strict_equality) and [`<`](/en-US/docs/Web/JavaScript/Reference/Operators/Less_than) that cannot be imported. | ||
- Most JavaScript functions are extremely permissive of the types of values they accept, and it's desirable to leverage WebAssembly's type system to remove those checks and coercions wherever we can. | ||
|
||
Considering these problems, creating built-in definitions that adapt existing JavaScript primitives to WebAssembly is simpler, more flexible, and better for performance than importing them. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not sure where you see increased flexibility, but the built-in definitions are certainly good for performance. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. OK, I've removed the flexibility part. It now reads:
|
||
|
||
## WebAssembly JavaScript builtin types | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. "## WebAssembly JavaScript Builtins" There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I've changed this to "## Available WebAssembly JavaScript builtins": I wanted to be clear that here we are discussing which builtins are available (which is what led me to use "types" in the first place ;-) ), rather than just discussing builtins in general. |
||
|
||
The first builtin types to be implemented are {{jsxref("String")}} operations. This is because the most pressing use case is languages that would benefit from using the JavaScript `String` type to implement their strings. You can find a list of the `String` operations with builtin equivalents defined for them at [JS String Builtin API](https://github.com/WebAssembly/js-string-builtins/blob/main/proposals/js-string-builtins/Overview.md#js-string-builtin-api). | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We're not implementing builtin types, but operations. As far as WebAssembly types are concerned, every imported string is just a generic There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. OK, understood. I've gone through the pages and made sure to find other ways to refer to "different available builtins" than "builtin types", given that "type" has a specific meaning that may confuse. |
||
|
||
Other primitive types are likely to be supported via builtins in the future. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. "Other builtins are likely to be supported in the future." It's not about types, and not about the JS concept of "primitive" types/values. For example, one of the next candidates could be There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good call; I've used your wording here. I've also scanned through the pages and made sure that there are no other problematic uses of "primitives" remaining. |
||
|
||
## How do you use builtins? | ||
|
||
Builtins work in a similar way to functions imported from JavaScript, except that you are using standard wasm function equivalents for performing JavaScript operations that are defined in a reserved namespace (`wasm:`). This being the case, browsers can predict and generate optimal code for them. | ||
|
||
Builtins are enabled at compile-time by specifying the `builtins` property during compilation. This goes inside the `compileOptions` object, and its value is an array of the builtin types you want to enable: | ||
|
||
```js | ||
WebAssembly.compile(bytes, { builtins: ["js-string"] }); | ||
``` | ||
|
||
The `compileOptions` object is available to the following functions: | ||
|
||
- [`WebAssembly.compile()`](/en-US/docs/WebAssembly/JavaScript_interface/compile_static) | ||
- [`WebAssembly.compileStreaming()`](/en-US/docs/WebAssembly/JavaScript_interface/compileStreaming_static) | ||
- [`WebAssembly.instantiate()`](/en-US/docs/WebAssembly/JavaScript_interface/instantiate_static) | ||
- [`WebAssembly.instantiateStreaming()`](/en-US/docs/WebAssembly/JavaScript_interface/instantiateStreaming_static) | ||
- [`WebAssembly.validate()`](/en-US/docs/WebAssembly/JavaScript_interface/validate_static) | ||
- The [`WebAssembly.Module()`](/en-US/docs/WebAssembly/JavaScript_interface/Module/Module) constructor | ||
|
||
You can also include the `importedStringConstants` property inside `compileOptions`, which enables and specifies an identifier for imported global string constants to be used by the builtins: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Below (line 104), you use the term "namespace". Might be good to use the same term here, to avoid confusion. Perhaps phrase it roughly like: "..., which selects one import namespace for imported global string constants that the engine will populate automatically". (You can use these imported string constants for anything you want, not just for the imported JS String Builtins. For example, you could import additional custom functions that work on strings.) |
||
|
||
```js | ||
WebAssembly.compile(bytes, { | ||
builtins: ["js-string"], | ||
importedStringConstants: "#", | ||
}); | ||
``` | ||
|
||
> [!NOTE] | ||
> The above example uses `"#"` as the imported global identifier for illustrative purposes. In production, however, it is best practice to use the empty string to save on module file size. The identifier is repeated for every string literal, and real-world modules can have tens of thousands of them, so the saving can be significant. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If you switch to the term "namespace" above, then please also switch here. Might also be worth mentioning that the choice is made by the authors of the toolchain that will generate the Wasm modules. Once you have a There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. OK, I've updated "identifier" to namespace in the different pages, and taken heed of your suggested wording. |
||
|
||
Over in your webassembly module, you can now import string literals, specifying the same identifier, and import builtins of the types specified in the `compileOptions` object from the `wasm:` namespace (in this case, the [`concat()`](/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/concat) function; see also the [equivalent built-in definition](https://github.com/WebAssembly/js-string-builtins/blob/main/proposals/js-string-builtins/Overview.md#wasmjs-string-concat)). | ||
|
||
```wasm | ||
(global $h (import "#" "hello ") externref) | ||
(global $w (import "#" "world!") externref) | ||
(func $concat (import "wasm:js-string" "concat") | ||
(param externref externref) (result (ref extern))) | ||
``` | ||
|
||
## Builtins example | ||
|
||
Let's work through a basic but complete example to show how builtins are used. This example will define a function inside a wasm module that concatenates two strings together and prints the result to the console, then export it. We will then call the exported function from JavaScript. | ||
|
||
The example we'll be referring to uses the [`WebAssembly.instantiate()`](/en-US/docs/WebAssembly/JavaScript_interface/instantiate_static) function on the webpage to handle the compilation and instantiation; you can find this and other examples on our `webassembly-examples` repo — see [`js-builtin-examples`](https://github.com/mdn/webassembly-examples/tree/main/js-builtin-examples). | ||
|
||
You can build up the example by following the steps below; also [see it running live](https://mdn.github.io/webassembly-examples/js-builtin-examples/instantiate/). | ||
|
||
### The JavaScript | ||
|
||
The JavaScript for the example is shown below. To test this locally, include it in an HTML page using a method of your chooosing (for example, inside {{htmlelement("script")}} tags, or in an external `.js` file referenced via `<script src="">`). | ||
|
||
```js | ||
const importObject = { | ||
// Regular import | ||
m: { | ||
log: console.log, | ||
}, | ||
}; | ||
|
||
const compileOptions = { | ||
builtins: ["js-string"], // Opt-in to get magic imported functions | ||
importedStringConstants: "#", // Opt-in to get magic imported globals | ||
}; | ||
|
||
fetch("log-concat.wasm") | ||
.then((response) => response.arrayBuffer()) | ||
.then((bytes) => WebAssembly.instantiate(bytes, importObject, compileOptions)) | ||
.then((result) => result.instance.exports.main()); | ||
``` | ||
|
||
The JavaScript: | ||
|
||
- Defines an `importObject` that specifies a function `"log"` at a namespace `"m"` to import into the wasm module during instantiation. It's the {{domxref("console.log()")}} function. | ||
- Defines a `compileOptions` function that includes the `builtins` and `importedStringConstants` properties. As discussed earlier, this is needed to enable builts and imported global string constants. | ||
- Uses {{domxref("Window.fetch", "fetch()")}} to fetch the wasm module (`log-concat.wasm`), converts the response to an {{jsxref("ArrayBuffer")}} using {{domxref("Response.arrayBuffer")}}, then compiles and instantiates the wasm module using [`WebAssembly.instantiate()`](/en-US/docs/WebAssembly/JavaScript_interface/instantiate_static). | ||
- Calls the `main()` function exported from the wasm module. | ||
|
||
### The wasm module | ||
|
||
The text representation of our WebAssembly module code looks like this: | ||
|
||
```wasm | ||
(module | ||
(global $h (import "#" "hello ") externref) | ||
(global $w (import "#" "world!") externref) | ||
(func $concat (import "wasm:js-string" "concat") | ||
(param externref externref) (result (ref extern))) | ||
(func $log (import "m" "log") (param externref)) | ||
(func (export "main") | ||
(call $log (call $concat (global.get $h) (global.get $w)))) | ||
) | ||
``` | ||
|
||
This code: | ||
|
||
- Imports two global string constants, `"hello "` and `"world!"`, with the `"#"` identifier as specified in the JavaScript. They are given names of `$h` and `$w`. | ||
- Imports the [`concat`](https://github.com/WebAssembly/js-string-builtins/blob/main/proposals/js-string-builtins/Overview.md#wasmjs-string-concat) builtin from the `wasm:` namespace, giving it a name of `$concat` and specifying that it has two parameters and a return value. | ||
- Imports the imported `"log"` function from the `"m"` namespace, as specified in the JavaScript `importObject` object, giving it a name of `$log` and specifying that it has a parameter. We decided to include a regular import as well as a builtin in the example, to show you how the two approaches compare. | ||
- Defines a function that will be exported with the name `"main"`. This function calls `$log`, passing it a `$concat` call as a parameter. The `$concat` call is passed the `$h` and `$w` global string constants as parameters. | ||
|
||
To get your local example working: | ||
|
||
1. Save the WebAssembly module code shown above into a text file called `log-concat.wat`, in the same directory as your HTML/JavaScript. | ||
2. Compile it into a WebAssembly module (`log-concat.wasm`) using the `wasm-as` tool, which is part of the [Binaryen library](https://github.com/WebAssembly/binaryen) (see the [build instructions](https://github.com/WebAssembly/binaryen?tab=readme-ov-file#building)). You'll need to run `wasm-as` with reference types and GC enabled for these examples to compile successfully: | ||
|
||
```sh | ||
wasm-as --enable-reference-types -–enable-gc log-concat.wat | ||
``` | ||
|
||
Or you can use the `-all` flag in place of `--enable-reference-types -–enable-gc`: | ||
|
||
```sh | ||
wasm-as -all log-concat.wat | ||
``` | ||
|
||
3. Load your example HTML page in a [supporting browser](/en-US/docs/WebAssembly/JavaScript_interface/instantiate_static#browser_compatibility) using a [local HTTP server](/en-US/docs/Learn/Common_questions/Tools_and_setup/set_up_a_local_testing_server#running_a_simple_local_http_server). | ||
|
||
The result should be a blank webpage, with `"hello world!"` logged to the JavaScript console, generated by an exported wasm function. The logging was done using a function imported from JavaScript, while the concatenation of the two original strings was done by a builtin. |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -18,13 +18,20 @@ This function is useful if it is necessary to compile a module before it can be | |
|
||
```js-nolint | ||
WebAssembly.compile(bufferSource) | ||
WebAssembly.compile(bufferSource, compileOptions) | ||
``` | ||
|
||
### Parameters | ||
|
||
- `bufferSource` | ||
- : A [typed array](/en-US/docs/Web/JavaScript/Guide/Typed_arrays) or {{jsxref("ArrayBuffer")}} | ||
containing the binary code of the Wasm module you want to compile. | ||
- `compileOptions` {{optional_inline}} | ||
- : An object containing compilation options. Properties can include: | ||
- `builtins` | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Same in the five other copies of this paragraph. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good call; I've updated these to add the "optional" label in my next commit. |
||
- : An array of strings that enables the usage of [WebAssembly JavaScript builtins](/en-US/docs/WebAssembly/JavaScript_builtins) in the compiled wasm module. The strings define the types of builtin you want to enable. Currently the only available value is `"js-string"`, which enables JavaScript string builtins. | ||
- `importedStringConstants` {{optional_inline}} | ||
- : A string specifying an identifier for imported global string constants. This property needs to be specified if you wish to use imported global string constants in the wasm module. | ||
|
||
### Return value | ||
|
||
|
@@ -59,6 +66,30 @@ fetch("simple.wasm") | |
> [`WebAssembly.compileStreaming()`](/en-US/docs/WebAssembly/JavaScript_interface/compileStreaming_static) in most cases, as it is more efficient | ||
> than `compile()`. | ||
|
||
### Enabling WebAssembly JavaScript builtins | ||
|
||
This example enables JavaScript string builtins and imported global string constants when compiling the wasm module, before instantiating it and running the exported `main()` function (which logs `"hello world!"` to the console). [See it running live](https://mdn.github.io/webassembly-examples/js-builtin-examples/compile/). | ||
|
||
```js | ||
const importObject = { | ||
// Regular import | ||
m: { | ||
log: console.log, | ||
}, | ||
}; | ||
|
||
const compileOptions = { | ||
builtins: ["js-string"], // Enable JavaScript string builtins | ||
importedStringConstants: "#", // Enable imported global string constants | ||
}; | ||
|
||
fetch("log-concat.wasm") | ||
.then((response) => response.arrayBuffer()) | ||
.then((bytes) => WebAssembly.compile(bytes, compileOptions)) | ||
.then((module) => WebAssembly.instantiate(module, importObject)) | ||
.then((instance) => instance.exports.main()); | ||
``` | ||
|
||
## Specifications | ||
|
||
{{Specifications}} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There's really only one problem, and that's performance overhead. The three detailed points below just list some of the reason why you need various wrapper functions that contribute to this overhead.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OK, rewritten as