The word “table” in WASM is not related to database tables, but to a list of function pointers, that can be called indirectly. This is how WASM handles callback functions, just like those used in JavaScript. It does contain a number of extra parts to set it up and can be more complicated, but hopefully by the end of this section you will be able to use them within your own code.
There are a number of different parts required in order to make everything work correctly. We will look at each one on their own but they need to all come together for them to work.
;; Create table of function references (table 4 funcref)
Here we are declaring a global table of function references.
This is done using the table
S-Expression, which gives the number of elements it will contain and what type of elements they are.
In this example we have created a table with 4 blank function references.
You can have any number of named tables, but only one single unnamed (default) table.
You can only have one default table in a module. We will use named tables later.
Before we can start adding function references to the table we need to create them. A function in the table needs to be of the same type when called, it must contain the same number of parameters and results, all with the same data types too. In order to make this happen we need to declare a function type, a sort of function prototype or design that each function we add to the table must follow.
;; We need to declare the signature of the function (what it looks like) ;; before we can call them indirectly (type $functionType (func (param $value i32) (result i32)) )
We can declare the function type using the type
S-Expression.
This contains the type label followed by a func
S-Expression which declares the parameters and result parts.
In this example we have a single parameter called $value
of type i32
, and it will return an i32
result.
When creating the functions they are required that we follow this same type.
To do this we can create the function using the type label.
;; First function with the $functionType signature (func $add2 (type $functionType) ;; Get the first parameter (which is 0). We cannot use the $value name local.get 0 ;; Add 2 to the parameter and push result on stack to be returned i32.const 2 i32.add ) ;; Second function with the $functionType signature (func $double (type $functionType) ;; Get the first parameter (which is 0). local.get 0 ;; Double it and push result on stack to be returned i32.const 2 i32.mul )
Here we have created two functions using the function type label instead of listing the same parameters and results. This makes sure the function is following the correct set of parameters and results. It is also required by the wat2wasm compiler.
When declaring the function we used the parameter $value
label, but because this does not come along when declaring the real functions,
we have to use the parameter index, not its label.
The first parameter has the index value 0.
In both functions we get the $value parameter, using index 0, and push it onto the stack.
This is then either added to or doubled. Each function pushes the result onto the stack.
We now have two functions and a table to put them in. Each part of a table is known as an element.
;; Set the table elements with the function pointers (elem (i32.const 0) $add2 $double)
The elem
S-Expression is used to initialise the default table with references to existing functions.
This first part is used to set the offset location within the table where the following functions will be placed.
After this is a list of all the functions that will be put into the table.
These are set at the beginning when the WASM instance is created.
We have now set up everything we need to use the table of functions. It is now possible to start calling them.
;; Push the parameters of the function on the stack first i32.const 4 ;; Then push the index of the function we want to call i32.const 1 ;; Call the function indirectly call_indirect (type $functionType) ;; The result is on the stack
Calling a function located in a table is similar to calling a normal function.
The first part is to push the parameters the function requires onto the stack.
The next part is different however.
You need to push the index of the function you want to call, the one in the table, onto the stack, then use the call_indirect
instruction.
This has the same function type as the function we want to call.
This is a way of making sure the function we want to call contains the same parameters and results used by the function in the table.
After the function has finished, you can process the result just like a normal function.
The above example pushes the function parameter, 4, onto the stack.
It then pushes the index of the function we want to call onto the stack, which is the $double
function, index 1 in the table.
This function is then called indirectly, doubles the parameter value 4, and pushes the result, 8, onto the stack.
The table does not need to contain functions that all have the same type. You can have different function types if you need, but calling them indirectly will require the function type given to match the one used by the function in the table.
The main reason for having tables is to allow us to use callback functions. For example, let's say we have a sort function that takes some parameters related to a list of data in memory, and also a compare callback function, which is called by the function when it needs to compare two items in memory. Creating something like this will require some extra steps. Below we will look at what it will take to create our own bubble sort function.
;; Import memory that contains the data to sort (import "import" "memory" (memory 1)) ;; Bubble sort function (func $bubbleSort (param $dataOffset i32) (param $dataItemSize i32) (param $dataItemCount i32) (param $compareCallback funcref) (result i32) ... )
There are two parts we need first.
The memory that contains the data we want to sort, and the $bubbleSort
function.
The first 3 parameters relate to the data in memory, where it starts, the size of each item in the list (in bytes), and number of items in the list.
The final parameter is the callback function, which has the data type of funcref
.
When using the function we need to pass a reference to a function, something we will look at later.
;; We need to declare the signature of the function (what it looks like) ;; before we can call them indirectly. This is for the compare ;; callback function (type $compareCallbackType (func (param $itemOffset1 i32) (param $itemOffset2 i32) (result i32)) )
The compare callback function needs to be of a certain type, it needs to fit a function design prototype. As you can see above, the function takes two parameters, the memory offset for one item, and the offset to the second. It is expected that the callback function gets the two items from memory, compares them both and then returns either -1 (item 1 is less than item 2), +1 (item 1 is greater than item 2) or 0 (item 1 is equal to item 2).
The parameters are memory offset locations, therefore it does not matter what the items are, what type of data it contains. The bubble sort function makes the callback function do the item comparison and just handles the result returned. The function can be used for any data types, as long as they are all the same size.
;; Create callback table (table $tableBubbleSortCallback 1 funcref)
We need to create a table to place the callback function into, however we do not want to create a default table, as there may already be one. What we want is our own table, that only the bubble sort function will use. This can be done by giving the table a label, like the above example. This way the WASM module can contain any number of named tables, all independent of each other.
;; Put the callback function reference into the table (at index 0) i32.const 0 local.get $compareCallback table.set $tableBubbleSortCallback
Inside the bubble sort function we need to add the callback function to the table, the function that was given as a parameter.
To do this we need to use the table.set
instruction.
You need to push the index of the element in the table you want to set, as there is only 1 element in the table this will be 0, and the compare callback function reference.
We can now call the callback function when needed.
;; Call the compare function (in table at index 0) local.get $itemOffset local.get $nextItemOffset i32.const 0 call_indirect $tableBubbleSortCallback (type $compareCallbackType)
Here we are first pushing the parameters of the compare function onto the stack.
This is the memory offset for item 1 and item 2, the two items we want to compare.
We then set which function in the table we want to call, and because there is only one function in the table, this is index 0.
This is followed by using the call_indirect
instruction, which we have used with the name of the table where the function is stored.
Using the bubble sort function does require some additional steps too. Below is an example of a compare callback function used with items that are single bytes.
;; Compare byte (func $compareByte (type $compareCallbackType) ;; Set locals (local $value1 i32) (local $value2 i32) ;; Get value 1 local.get 0 i32.load8_u local.set $value1 ;; Get value 2 local.get 1 i32.load8_u local.set $value2 ;; If value 1 is greater than value 2 local.get $value1 local.get $value2 i32.gt_u if ;; Return 1 i32.const 1 return end ;; If value 1 is less than value 2 local.get $value1 local.get $value2 i32.lt_u if ;; Return -1 i32.const -1 return end ;; They are the same, so return 0 i32.const 0 )
A reference to this callback function will be passed to the bubble sort function. It works by getting the byte values from memory at the offsets given, comparing them and then returning the result. Before we can use it, we need to tell WASM that the function can be used as a callback.
;; Declare the element compare callback functions that can be used (elem declare func $compareByte)
Here we are declaring a new element that could be placed into a table and doing it in a way that doesn't require it to be automatically inserted into a table. We are only telling WASM that the function could be put into a table. There is no need to state which table it will go into or which index in the table it should be placed at.
We can now put everything together and call the bubble sort function with our own callback function.
;; Push the data offset i32.const 0 ;; Push the size of each item (1 byte) i32.const 1 ;; Push the number of items in the list i32.const 200 ;; Push the function reference for the callback function ref.func $compareByte ;; Call the bubble sort function call $bubbleSort
Before calling the function we need to push information about the data onto the stack.
This includes the starting location in memory where the data is stored, the size of each item and the number of items in the list.
This is followed by the ref.func
instruction, which is used to push the reference to the $compareByte
function onto the stack.
Finally we call the bubble sort function, which takes all the parameters and goes through the sorting process by comparing items and moving them around in the list.
The use of callback functions in this way can be very useful for writing reusable functions, or returning control back to handle different tasks.