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 article explains how builtins work and which ones are available, then provides a usage example.
Problems with importing JavaScript functionsFor many JavaScript features, regular imports work OK. However, importing glue code for primitives such as String
, ArrayBuffer
, and Map
comes with significant performance overheads. In such cases, 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.
Specifically, importing functions from JavaScript to WebAssembly modules creates performance problems for the following reasons:
this
value, which WebAssembly function import
calls leave as undefined
.===
and <
that cannot be imported.Considering these problems, creating built-in definitions that adapt existing JavaScript functionality such as String
primitives to WebAssembly is simpler and better for performance than importing it and relying on indirect function calls.
The below sections detail the available builtins. Other builtins are likely to be supported in the future.
String operationsThe available String
builtins are:
"wasm:js-string" "cast"
Throws an error if the provided value is not a string. Roughly equivalent to:
if (typeof obj !== "string") throw new WebAssembly.RuntimeError();
"wasm:js-string" "compare"
Compares two string values and determines their order. Returns -1
if the first string is less than the second, 1
if the first string is greater than the second, and 0
if the strings are strictly equal.
"wasm:js-string" "concat"
Equivalent to String.prototype.concat()
.
"wasm:js-string" "charCodeAt"
Equivalent to String.prototype.charCodeAt()
.
"wasm:js-string" "codePointAt"
Equivalent to String.prototype.codePointAt()
.
"wasm:js-string" "equals"
Compares two string values for strict equality, returning 1
if they are equal, and 0
if not.
Note: The "equals"
function is the only string builtin that doesn't throw for null
inputs, so Wasm modules don't need to check for null
values before calling it. All the other functions have no reasonable way to handle null
inputs, so they throw for them.
"wasm:js-string" "fromCharCode"
Equivalent to String.fromCharCode()
.
"wasm:js-string" "fromCharCodeArray"
Creates a string from a Wasm array of i16
values.
"wasm:js-string" "fromCodePoint"
Equivalent to String.fromCodePoint()
.
"wasm:js-string" "intoCharCodeArray"
Writes a string's char codes into a Wasm array of i16
values.
"wasm:js-string" "length"
Equivalent to String.prototype.length
.
"wasm:js-string" "substring"
Equivalent to String.prototype.substring()
.
"wasm:js-string" "test"
Returns 0
if the provided value is not a string, or 1
if it is a string. Roughly equivalent to:
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. This section summarizes how to use them.
Builtins are enabled at compile-time by specifying the compileOptions.builtins
property as an argument when calling methods to compile and/or instantiate a module. Its value is an array of strings that identify the sets of builtins you want to enable:
WebAssembly.compile(bytes, { builtins: ["js-string"] });
The compileOptions
object is available to the following functions:
WebAssembly.compile()
WebAssembly.compileStreaming()
WebAssembly.instantiate()
WebAssembly.instantiateStreaming()
WebAssembly.validate()
WebAssembly.Module()
constructorOver in your WebAssembly module, you can now import builtins as specified in the compileOptions
object from the wasm:
namespace (in this case, the concat()
function; see also the equivalent built-in definition):
(func $concat (import "wasm:js-string" "concat")
(param externref externref) (result (ref extern)))
Feature detecting builtins
When using builtins, type checks will be stricter than when they are not present â certain rules are imposed on the builtin imports.
Therefore, to write feature detection code for builtins you can define a module that's invalid with the feature present, and valid without it. You then return true
when validation fails, to indicate support. A basic module that will achieve this is as follows:
(module
(function (import "wasm:js-string" "cast")))
Without builtins, the module is valid, because you can import any function with any signature you want (in this case: no parameters and no return values). With builtins, the module is invalid, because the now-special-cased "wasm:js-string" "cast"
function must have a specific signature (an externref
parameter and a non-nullable (ref extern)
return value).
You can then try validating this module with the validate()
method, but note how the result is negated with the !
operator â remember that builtins are supported if the module is invalid:
const compileOptions = {
builtins: ["js-string"],
};
fetch("module.wasm")
.then((response) => response.arrayBuffer())
.then((bytes) => WebAssembly.validate(bytes, compileOptions))
.then((result) => console.log(`Builtins available: ${!result}`));
The above module code is so short that you could just validate the literal bytes rather than downloading the module. A feature detection function could look like so:
function JsStringBuiltinsSupported() {
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: In many cases there are alternatives to feature detecting builtins. Another option could be to provide regular imports alongside the builtins, and supporting browsers will just ignore the fallbacks.
Builtins exampleLet'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()
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
.
You can build up the example by following the steps below. In addition, you can see it running live â open your browser's JavaScript console to see the example output.
JavaScriptThe JavaScript for the example is shown below. To test this locally, include it in an HTML page using a method of your choosing (for example, inside <script>
tags, or in an external .js
file referenced via <script src="">
).
const importObject = {
// Regular import
m: {
log: console.log,
},
};
const compileOptions = {
builtins: ["js-string"], // Enable JavaScript string builtins
importedStringConstants: "string_constants", // Enable imported global string constants
};
fetch("log-concat.wasm")
.then((response) => response.arrayBuffer())
.then((bytes) => WebAssembly.instantiate(bytes, importObject, compileOptions))
.then((result) => result.instance.exports.main());
The JavaScript:
importObject
that specifies a function "log"
at a namespace "m"
to import into the Wasm module during instantiation. It's the console.log()
function.compileOptions
object that includes:
builtins
property to enable string builtins.importedStringConstants
property to enable imported global string constants.fetch()
to fetch the Wasm module (log-concat.wasm
), converts the response to an ArrayBuffer
using Response.arrayBuffer
, then compiles and instantiates the Wasm module using WebAssembly.instantiate()
.main()
function exported from the Wasm module.The text representation of our WebAssembly module code looks like this:
(module
(global $h (import "string_constants" "hello ") externref)
(global $w (import "string_constants" "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:
"hello "
and "world!"
, with the "string_constants"
namespace as specified in the JavaScript. They are given names of $h
and $w
.concat
builtin from the wasm:
namespace, giving it a name of $concat
and specifying that it has two parameters and a return value."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."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:
Save the WebAssembly module code shown above into a text file called log-concat.wat
, in the same directory as your HTML/JavaScript.
Compile it into a WebAssembly module (log-concat.wasm
) using the wasm-as
tool, which is part of the Binaryen library (see the build instructions). You'll need to run wasm-as
with reference types and garbage collection (GC) enabled for these examples to compile successfully:
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
:
wasm-as -all log-concat.wat
Load your example HTML page in a supporting browser using a 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.
RetroSearch is an open source project built by @garambo | Open a GitHub Issue
Search and Browse the WWW like it's 1997 | Search results from DuckDuckGo
HTML:
3.2
| Encoding:
UTF-8
| Version:
0.7.4