Introduction to WebAssembly Text

Previous   Next
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 i32 value.

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.

Naming Functions

A function is declared using the keyword func.


IMPORTANT

WebAssembly functions can be given two, possibly different, human-readable names:1

  1. An internal name, visible only to other functions within the WebAssembly module
  2. 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 ($real and $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 $real and $imag

09-single-return-value.wat

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

After the 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:

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.

09-multiple-return-values.wat

;; 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 09-multiple-return-values.wat

wasmer 09-multiple-return-values.wat -i conj -- -5 3
-5 -3

IMPORTANT

Notice the double hyphens -- between -i conj and the function arguments.
This is necessary to prevent the shell from interpreting the minus sign in front of -5 as a shell option

NodeJS Limitation

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.


  1. 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 2 or 7 or 21 does. Good luck with that one…