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

Conversation

chrisdavidmills
Copy link
Contributor

@chrisdavidmills chrisdavidmills commented Dec 13, 2024

Description

Chrome 130 supports WebAssembly JS String builtins.

Specifically, This PR adds:

  • A guide explaining what builtins are and how to use them.
  • Information about the new compileOptions parameter used to enable these built-ins, which is available to:
    • WebAssembly.compile()
    • WebAssembly.compileStreaming()
    • WebAssembly.instantiate()
    • WebAssembly.instantiateStreaming()
    • WebAssembly.validate()
    • The WebAssembly.Module() constructor

Motivation

Additional details

See https://chromestatus.com/feature/6695587390423040 for the data source.

Related issues and pull requests

@chrisdavidmills chrisdavidmills requested review from a team as code owners December 13, 2024 09:53
@chrisdavidmills chrisdavidmills requested review from hamishwillee and removed request for a team December 13, 2024 09:53
@github-actions github-actions bot added the Content:wasm WebAssembly docs label Dec 13, 2024
@chrisdavidmills chrisdavidmills marked this pull request as draft December 13, 2024 09:53
@github-actions github-actions bot added the size/m [PR only] 51-500 LoC changed label Dec 13, 2024
Copy link
Contributor

github-actions bot commented Dec 13, 2024

Preview URLs (8 pages)
Flaws (1)

Note! 7 documents with no flaws that don't need to be listed. 🎉

URL: /en-US/docs/WebAssembly/JavaScript_builtins
Title: WebAssembly JavaScript builtins
Flaw count: 1

  • macros:
    • /en-US/docs/Web/API/Console/log redirects to /en-US/docs/Web/API/console/log_static
External URLs (10)

URL: /en-US/docs/WebAssembly/JavaScript_builtins
Title: WebAssembly JavaScript builtins


URL: /en-US/docs/WebAssembly/JavaScript_interface/compile_static
Title: WebAssembly.compile()


URL: /en-US/docs/WebAssembly/JavaScript_interface/compileStreaming_static
Title: WebAssembly.compileStreaming()


URL: /en-US/docs/WebAssembly/JavaScript_interface/instantiateStreaming_static
Title: WebAssembly.instantiateStreaming()


URL: /en-US/docs/WebAssembly/JavaScript_interface/validate_static
Title: WebAssembly.validate()


URL: /en-US/docs/WebAssembly/JavaScript_interface/instantiate_static
Title: WebAssembly.instantiate()


URL: /en-US/docs/WebAssembly/JavaScript_interface/Module/Module
Title: WebAssembly.Module() constructor

(comment last updated: 2024-12-13 13:45:39)

@chrisdavidmills chrisdavidmills marked this pull request as ready for review December 13, 2024 13:32
```

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

WebAssembly.instantiate(bufferSource, importObject);
WebAssembly.instantiate(bufferSource, compileOptions);

Choose a reason for hiding this comment

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

Nope, this won't work. compileOptions, if present, must be the third argument. You'll have to pass an empty object ({}) as importObject if the module needs no imports (which is exceedingly rare in practice).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

D'oh, that was silly. I have fixed these instances.

WebAssembly.instantiate(module, importObject);
WebAssembly.instantiate(module, compileOptions);

Choose a reason for hiding this comment

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

Same here: compileOptions, if present, must be the third parameter (as in line 80).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fixed

@@ -19,6 +19,8 @@ is the most efficient, optimized way to load Wasm code.

```js-nolint
WebAssembly.instantiateStreaming(source, importObject)
WebAssembly.instantiateStreaming(source, compileOptions)

Choose a reason for hiding this comment

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

Same here: compileOptions must be the third parameter.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fixed.

@@ -53,6 +60,22 @@ fetch("simple.wasm")
});
```

### Feature detecting WebAssembly JavaScript builtins

This example validates a wasm module with JavaScript string builtins and imported global string constants enabled, logging `"wasm module valid: true"` to the console if it is valid, and `"wasm module valid: false"` if it isn't. [See it running live](https://mdn.github.io/webassembly-examples/js-builtin-examples/validate/).

Choose a reason for hiding this comment

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

Yes, but that's not at all how you'd feature-detect the JS String Builtins. When this feature is present, then type checks will be stricter than without it (because it imposes certain rules on the imports that will get "magic" treatment).

So to detect this feature, you define a module that's invalid with the feature present, but valid without it, and return true when validation failed. A simple/short way to do this is:

(module
  (function (import "wasm:js-string" "cast")))

Without JS String Builtins, that's perfectly valid, because you can import any function you want with any signature you want (in this case: no parameters and no return values).
With JS String Builtins, this module is invalid, because the now-special-cased "wasm:js-string" "cast" function must have a specific signature (consuming an externref, returning a non-nullable (ref extern)).

In wire bytes, that module is so short that you can just embed the literal:

function IsJsStringBuiltinsSupported() {
  let bytes = new Uint8Array([
    0,   97,  115, 109, 1,   0,   0,  0,   1,   4,   1,   96,  0,
    0,   2,   23,  1,   14,  119, 97, 115, 109, 58,  106, 115, 45,
    115, 116, 114, 105, 110, 103, 4,  99,  97,  115, 116, 0,   0
  ]);
  return !WebAssembly.validate(bytes, {builtins: ["js-string"]});  // Note '!'!
}

That said, detecting this feature is often not even necessary, because it can so easily be polyfilled. You can simply provide real imports to take the place of the magic imports, and if the engine supports JS String Builtins, it'll ignore those fallbacks.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ah, thanks, this is really helpful! I've repurposed my existing example as a basic "this is how to validate a module along with builtins", and taken the liberty of including your above explanation as an additional feature detection example. I hope you don't mind, but this is too useful to not include.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It'll be present when I push my next commit.


## WebAssembly JavaScript builtin types

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.


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

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.


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.

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

```

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

- [`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.)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Content:wasm WebAssembly docs size/m [PR only] 51-500 LoC changed
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants