What is a WebAssembly Instance?
A WebAssembly instance is an object created from a module that:
- Allocates resources such as memory or tables as specified by the module.
- Links the module’s imports (functions or memory from the host environment).
- Provides access to the module’s exported functions, memory or global variables.
In simple terms, an instance is how WebAssembly code interacts with the outside world, such as JavaScript or other WebAssembly modules.
Key Features of WebAssembly Instances
- Dynamic: You can create multiple instances of the same module, each with its own state and memory.
- Reusable: The same instance can be used repeatedly in different parts of an application.
- Efficient: Instances are lightweight and optimized for performance.
- Isolated: Each instance operates independently unless explicitly designed to share memory.
How to Create a WebAssembly Instance
WebAssembly instances are created using the JavaScript WebAssembly.instantiate() function. This function takes a module and an optional set of imports as arguments and returns a promise resolving to an instance.
Steps to Create an Instance:
- Fetch the Module: Download the .wasm file containing the WebAssembly module.
- Compile the Module: Convert the binary data of the .wasm file into a WebAssembly module object.
- Instantiate the Module: Link the module with any necessary imports and create an instance.
Example: Creating a WebAssembly Instance
WebAssembly Text Format (WAT)
Let’s start with a simple WebAssembly module:
(module
(func $add (param $a i32) (param $b i32) (result i32)
local.get $a
local.get $b
i32.add
)
(export "add" (func $add))
)
The module defines a function add that takes two integers and returns their sum. It also exports the function to make it available for the host environment.
JavaScript Code to Create an Instance
// Fetch the WebAssembly module
fetch('example.wasm')
.then(response => response.arrayBuffer())
.then(bytes => WebAssembly.instantiate(bytes))
.then(({ instance }) => {
// Access the exported 'add' function
console.log(instance.exports.add(10, 20)); // Output: 30
})
.catch(console.error);
Explanation:
- The fetch() function retrieves the .wasm file.
- The arrayBuffer() method converts the response into a binary format.
- WebAssembly.instantiate() creates a module and instance in one step.
- The exports object provides access to the module’s exported functions, like add.
Linking Imports and Instances
WebAssembly modules often rely on imports to access functionality provided by the host environment. These imports are specified when instantiating the module.
Example: Adding Imports to an Instance
WebAssembly Module:
(module
;; Import a logging function from JavaScript
(import "js" "log" (func $log (param i32)))
(func $call_log (param $x i32)
local.get $x
call $log
)
(export "call_log" (func $call_log))
)
JavaScript Code:
const imports = {
js: {
log: (x) => console.log("Logged value:", x),
},
};
fetch('import_example.wasm')
.then(response => response.arrayBuffer())
.then(bytes => WebAssembly.instantiate(bytes, imports))
.then(({ instance }) => {
instance.exports.call_log(42); // Output: Logged value: 42
})
.catch(console.error);
Memory in Instances
Instances can include memory, which is used for storing data that the module works with. Memory is defined in the module and initialized when the instance is created.
Defining Memory in a Module:
(module
(memory (export "mem") 1) ;; Define 1 page (64 KB) of memory
(data (i32.const 0) "Hello, WebAssembly!") ;; Store a string at memory offset 0
)
Accessing Memory in JavaScript:
fetch('memory_example.wasm')
.then(response => response.arrayBuffer())
.then(bytes => WebAssembly.instantiate(bytes))
.then(({ instance }) => {
const memory = new Uint8Array(instance.exports.mem.buffer);
const message = new TextDecoder().decode(memory);
console.log(message); // Output: Hello, WebAssembly!
});
Multiple Instances of the Same Module
You can create multiple instances of the same module. Each instance has its own state, memory, and imported dependencies.
Example:
const imports1 = { js: { log: (x) => console.log("Instance 1 log:", x) } };
const imports2 = { js: { log: (x) => console.log("Instance 2 log:", x) } };
fetch('example.wasm')
.then(response => response.arrayBuffer())
.then(bytes => WebAssembly.compile(bytes))
.then((module) => {
return Promise.all([
WebAssembly.instantiate(module, imports1),
WebAssembly.instantiate(module, imports2),
]);
})
.then(([instance1, instance2]) => {
instance1.exports.call_log(10); // Output: Instance 1 log: 10
instance2.exports.call_log(20); // Output: Instance 2 log: 20
})
.catch(console.error);
Benefits of WebAssembly Instances
- Modularity: Instances allow developers to isolate functionality and manage dependencies easily.
- Flexibility: Each instance can be tailored with specific imports, making it reusable in different contexts.
- Scalability: Creating multiple instances of a module helps manage state in larger applications.
- Performance: Instances are optimized for rapid initialization and efficient execution.