Skip to content
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

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
149 changes: 149 additions & 0 deletions files/en-us/webassembly/javascript_builtins/index.md
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:

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.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK, rewritten as

There are several reasons why importing functions from JavaScript to WebAssembly modules introduce significant performance overheads:


- 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.

Choose a reason for hiding this comment

The 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.

Copy link
Contributor Author

@chrisdavidmills chrisdavidmills Dec 13, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK, I've removed the flexibility part. It now reads:

Considering these problems, creating built-in definitions that adapt existing JavaScript primitives to WebAssembly is simpler and better for performance than importing them.


## WebAssembly JavaScript builtin types

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"## WebAssembly JavaScript Builtins"

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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).

Choose a reason for hiding this comment

The 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 externref.

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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.

Choose a reason for hiding this comment

The 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 DataView-related operations, but that's totally TBD (i.e. probably too early to mention here).

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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:

Choose a reason for hiding this comment

The 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.

Choose a reason for hiding this comment

The 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 .wasm file and want to embed it in your JavaScript, you can't freely choose this namespace any more; you have to use what that .wasm file expects.

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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
Expand Up @@ -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`

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

builtins is just as optional as importedStringConstants, so this line should have {{optional_inline}}.

Same in the five other copies of this paragraph.

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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

Expand Down Expand Up @@ -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}}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,19 @@ This function is useful if it is necessary to compile a module before it can be

```js-nolint
WebAssembly.compileStreaming(source)
WebAssembly.compileStreaming(source, compileOptions)
```

### Parameters

- `source`
- : A [`Response`](/en-US/docs/Web/API/Response) object or a promise that will fulfill with one, representing the underlying source of a Wasm module you want to stream and compile.
- `compileOptions` {{optional_inline}}
- : An object containing compilation options. Properties can include:
- `builtins`
- : 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

Expand Down Expand Up @@ -55,6 +62,28 @@ WebAssembly.compileStreaming(fetch("simple.wasm"))
The resulting module instance is then instantiated using
[`WebAssembly.instantiate()`](/en-US/docs/WebAssembly/JavaScript_interface/instantiate_static), and the exported function invoked.

### 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-streaming/).

```js
const importObject = {
// Regular import
m: {
log: console.log,
},
};

const compileOptions = {
builtins: ["js-string"], // Enable JavaScript string builtins
importedStringConstants: "#", // Enable imported global string constants
};

WebAssembly.compileStreaming(fetch("log-concat.wasm"), compileOptions)
.then((module) => WebAssembly.instantiate(module, importObject))
.then((instance) => instance.exports.main());
```

## Specifications

{{Specifications}}
Expand Down
8 changes: 4 additions & 4 deletions files/en-us/webassembly/javascript_interface/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,14 +44,14 @@ The primary uses for the `WebAssembly` object are:

## Static methods

- [`WebAssembly.instantiate()`](/en-US/docs/WebAssembly/JavaScript_interface/instantiate_static)
- : The primary API for compiling and instantiating WebAssembly code, returning both a `Module` and its first `Instance`.
- [`WebAssembly.instantiateStreaming()`](/en-US/docs/WebAssembly/JavaScript_interface/instantiateStreaming_static)
- : Compiles and instantiates a WebAssembly module directly from a streamed underlying source, returning both a `Module` and its first `Instance`.
- [`WebAssembly.compile()`](/en-US/docs/WebAssembly/JavaScript_interface/compile_static)
- : Compiles a [`WebAssembly.Module`](/en-US/docs/WebAssembly/JavaScript_interface/Module) from WebAssembly binary code, leaving instantiation as a separate step.
- [`WebAssembly.compileStreaming()`](/en-US/docs/WebAssembly/JavaScript_interface/compileStreaming_static)
- : compiles a [`WebAssembly.Module`](/en-US/docs/WebAssembly/JavaScript_interface/Module) directly from a streamed underlying source, leaving instantiation as a separate step.
- [`WebAssembly.instantiate()`](/en-US/docs/WebAssembly/JavaScript_interface/instantiate_static)
- : The primary API for compiling and instantiating WebAssembly code, returning both a `Module` and its first `Instance`.
- [`WebAssembly.instantiateStreaming()`](/en-US/docs/WebAssembly/JavaScript_interface/instantiateStreaming_static)
- : Compiles and instantiates a WebAssembly module directly from a streamed underlying source, returning both a `Module` and its first `Instance`.
- [`WebAssembly.validate()`](/en-US/docs/WebAssembly/JavaScript_interface/validate_static)
- : Validates a given typed array of WebAssembly binary code, returning whether the bytes are valid WebAssembly code (`true`) or not (`false`).

Expand Down
Loading
Loading