Introduction to WebAssembly Text
|Loops||Up||WASM and Shared Memory|
9: More About Functions
So far, we have only looked at a very simple WebAssembly function — one that takes no arguments and returns a single
For the sake of simplicity, we will skip over certain details of function declarations such as interface types, as these are not required at the moment.
A function is declared using the keyword
WebAssembly functions can be given two, possibly different, human-readable names:1
- An internal name, visible only to other functions within the WebAssembly module
- An external name, visible only from outside the WebAssembly module.
Taken together, the set of all external function names form the WebAssembly module’s “Public API”
If we wish to call a WebAssembly function from some other function inside the same module, then we can declare an internal name:
(func $my_func ;; The function's private, internal name ;; Function body goes here )
However, if a WebAssembly function is part of the module’s public API (I.E. it must be callable from outside the module), then the internal name can be omitted. The external name is defined using the
export keyword followed by some string that becomes the function’s public (or exported) name:
(func (export "wasm_fn") ;; The function's public, external name ;; Function body goes here )
It also might be the case that we need to call this function from both inside and outside the module. In this case, specify both names (which could well be different):
(func $my_func ;; The function's private, internal name (export "wasm_fn") ;; The function's public, external name ;; Function body goes here )
Function Arguments and Return Values
If a WebAssembly function needs to be passed any arguments, these must be specified immediately after the function name(s). Similarly, if a function returns any values, these values’ data types must be specified after any arguments have been defined.
Here’s a real-life example. Let’s say we have a function that finds the magnitude (or hypotenuse length) of a complex number; that is, the distance from origin to the point on the complex plane.
This function takes a complex number as an argument in the form of two, 64-bit floating point numbers (
$imag) and returns a single 64-bit floating point number:
(func $mag ;; Internal name (export "mag") ;; External name (param $real f64) ;; 1st argument is an f64 known as $real (param $imag f64) ;; 2nd argument is an f64 known as $imag (result f64) ;; One f64 will be left on the stack when the function terminates ;; Function body goes here )
The implementation of this function simply uses Pythagoras’ formula to work out the hypotenuse length of the right triangle having sides
(func $mag ;; Internal name (export "mag") ;; External name (param $real f64) ;; 1st argument is an f64 known as $real (param $imag f64) ;; 2nd argument is an f64 known as $imag (result f64) ;; One f64 will be left on the stack when the function terminates ;; Find the square root of the top value on the stack, then push the result ;; back onto the stack (f64.sqrt ;; Pop the top two values off the stack, add them up and push the result back ;; onto the stack (f64.add ;; Square the real part and push the result onto the stack (f64.mul (local.get $real) (local.get $real)) ;; Square the imaginary part and push the result onto the stack (f64.mul (local.get $imag) (local.get $imag)) ) ) )
f64.sqrt instruction has been executed, it pushes its result onto the stack, then the function exits. The value left on the stack implicitly becomes the function’s return value.
You can run this function using
wasmer 09-single-return-value.wat -i mag 3 4 5
Functions with Multiple Return Values
WebAssembly functions can also return multiple values. Here’s a simple example in which we calculate the conjugate of complex number. This is a very simple operation that transforms the complex number
a + bi into
a - bi.
The WebAssembly function is passed a complex number in the form of two, 64-bit floating point numbers, and it returns another complex number, also in the form of two, 64-bit floating point numbers.
;; Conjugate of a complex number ;; conj(a+bi) => (a-bi) (func $conj ;; Internal name (export "conj") ;; External name (param $a f64) ;; 1st argument is an f64 known as $a (param $b f64) ;; 2nd argument is an f64 known as $b (result f64 f64) ;; Two f64s will be left on the stack (local.get $a) ;; Push $a. Stack = [$a] (f64.neg (local.get $b)) ;; Push $b then negate its value. Stack = [-$b, $a] )
After calling this function, the host environment pops these two values off the stack to obtain the result of the function call
You can test this by running using
wasmer to run
wasmer 09-multiple-return-values.wat -i conj -- -5 3 -5 -3
Notice the double hyphens
-i conjand the function arguments.
This is necessary to prevent the shell from interpreting the minus sign in front of
-5as a shell option
Versions of NodeJS lower than 16 cannot invoke WebAssembly functions that return multiple values. Earlier NodeJS versions will throw a runtime error if you attempt to instantiate a WebAssembly module that exports a function with multiple return values.
If you have Node 14 installed, try running
09-multiple-return-values.js from NodeJs.
node 09-multiple-return-values.js (node:49384) UnhandledPromiseRejectionWarning: CompileError: WebAssembly.instantiate(): return count of 2 exceeds internal limit of 1 @+15 (Use `node --trace-warnings ...` to show where the warning was created)
In Node 16 or higher, this
.wasm file runs fine as it will do in
wasmer and from within a browser.
All functions (and local variables) are referenced by their index number, so you could choose not to use any human-readable function names; however, its now down to you to remember what function number
21does. Good luck with that one… ↩