A common method of sharing data between functions is by using global variables. A function can have local variables, but these are self contained and cannot be used outside. When you use a global variable, all functions have access to it, allowing them to read and write to it whenever needed. It can be used in many different ways to solve all manner of complex programming challenges.
Before we look at global variables we need to take a quick look at how local variables are declared and used. Right after the start of a function you can declare a list of local variables. These are only used within the function. They are created using the following S-Expression.
(local $name i32)
It starts with the keyword local
followed by the name label and its data type.
You can have any number of local variables of any data type you need. Any data type can be used.
You need to declare a global variable before it can be used. You have the option to create a global that is linked outside of the WASM code, so that it can be seen and used within JavaScript, or create one that is only used internally. Here we will look at internal global variables. To create one you need to give it a name, a data type, state if it is immutable (cannot change) or mutable (can change) and also set its starting value.
Let's take a look at a global variable and see how it is declared.
(global $global_1 i32 (i32.const 42))
This is an S-Expression, which starts with the keyword global
, and is followed by a number of other parts.
The $global_1
label is used when reading from and writing to the variable.
This is followed by the data type, in this case an i32
integer.
The last part is an internal S-Expression that is used to set the starting (default) value of the global variable when the WASM instance is created.
(global $global_2 (mut i32) (i32.const 101))
In this example we are creating a $global_2
global variable, but this time the data type is inside an child S-Expression which uses the keyword mut
.
This is used to state that the variable is mutable, that it can be changed.
By default all global variables are immutable, they cannot be changed, and therefore act like a global constant.
;; Global constant (immutable, cannot be changed) (global $global_1 i32 (i32.const 42)) ;; Global variable (mutable, can be changed) (global $global_2 (mut i32) (i32.const 101)) (func $getGlobal2 (result i32) ;; Get $global_2 value and push it onto the stack global.get $global_2 ) (func $addToGlobal2 (param $value i32) ;; Add $value to $global_1 local.get $value global.get $global_1 i32.add ;; Set $global_2 with the result global.set $global_2 )
In this example we are both reading from and writing to global variables.
When reading from function parameters, or local variables, you use the local.get
instruction followed by the name of the variable.
Working with global variables is similar, but instead of the keyword local
you need to use the word global
, for
example global.get
and global.set
.
You can declare a global variable with JavaScript, have it imported into WASM, and used internally just like any other variable. This can be done in a number of different ways. In this section we will look at constants.
;; Import global from a literal (import "import" "globalFromLiteral" (global $importGlobalFromLiteral i32))
The first part requires you to declare the global variable within WAT, giving the import details, the name of the global variable and its data type.
The S-Expression starts off with the import
keyword followed by the name of the object and the property name it will use to
look for the imported global variable.
// Set options const options = { import: { globalFromLiteral: 99 } } // Instantiate the WASM data const promise = WebAssembly.instantiate(wasmData, options);
When creating the WASM instance you can pass an options object. This contains the object and property we are looking for when importing the global variable. I have named them “import” and “globalFromLiteral”, but they can be named anything you want. In this example, we are giving the global variable the integer value of 99. This is immutable, cannot be changed, and is therefore read only.
This method does not use a WebAssembly.Global
object.
You do not need to create one of these objects to pass global variable data into a WASM instance, you can just use standard numeric literals.
It only works with i32
, i64
(with BigInt), f32
and f64
data types.
Another way to use a global variable in JavaScript is to create a WebAssembly.Global
object.
This can be setup to be mutable or immutable, so that it is either changeable or a fixed constant.
// Create some WASM global objects const globalMutableTrue = new WebAssembly.Global( { value: 'i32', mutable: true }, 404); const globalMutableFalse = new WebAssembly.Global( { value: 'i32', mutable: false }, 500); // Set options const options = { import: { globalMutableTrue: globalMutableTrue globalMutableFalse: globalMutableFalse } } // Instantiate the WASM data const promise = WebAssembly.instantiate(wasmData, options);
We are creating two global variable objects, both of which have the data type of i32
, and one is set up as mutable while the other is not.
They also have starting values set.
Both global objects are then added to the options object before it is used to create the WASM instance.
;; Import global from a literal (import "import" "globalMutableFalse" (global $importGlobalMutableFalse i32) ) ;; Import global from a WebAssembly.Global object (with mutable = true) (import "import" "globalMutableTrue" (global $importGlobalMutableTrue (mut i32)) )
We also need to declare the global variables within the WAT source code.
It looks the same as before, however the second mutable global variable has some extra settings on the end, using the mut
keyword to mark it as mutable.
You will need to make sure the name, mutable setting and the data types all match up. The ones used to create the global objects in JavaScript must match those declared within WASM.
// Write to global variable globalMutableTrue.value = 42; // Perform some internal process with global // Read and log the global variable console.log(globalMutableTrue.value);
You can use the global variable by accessing the value
property.
Setting the value will automatically reset the global variable inside the WASM instance.
You could perform a number of operations on the global variable internally and even reset its value to something different.
This will also change the value of the global variable seen within JavaScript.
This is a quick and simple way of transferring small amounts of data between JavaScript and the internal parts of WASM.
Another benefit of using a global variable, with the WebAssembly.Global
object, is that it can be shared with more than one WASM instance.
You could even share the same global variable between totally different WASM modules.
// Create a WASM global year object const globalYear = new WebAssembly.Global( { value: 'i32', mutable: true }, 2038); // Set options const options = { import: { year: globalYear } } // Instantiate the WASM data const report1 = await WebAssembly.instantiate(report1WasmData, options); const report2 = await WebAssembly.instantiate(report2WasmData, options);
We are creating a single global variable object that is then used in two different WASM instances.
All we need to do is set the year
value in JavaScript and all the WASM instances will see the same value.
;; Import global year from a WebAssembly.Global object (with mutable = true) (import "import" "year" (global $year (mut i32)))
Because the global variable is mutable, if one of the instances changes the year value, it will be changed in all the other WASM instances and in the JavaScript global object. The value is seen by all the different parts that use it and they can all change its value too. It behaves like there is a single value that they can all have access to.