Implementing an array


In Creating memory, you know that, WebAssembly provides i32, i64, f32 and f64. If you want to create an array, you have to define what an array is in memory and how to access it.

For simplicity, consider an array composed of only i32. The first i32 is used to record the beginning offset of available space. After that, every array uses an i32 to record its length and the remaining bytes is used to store elements.

For example, if there're two arrays in memory, one has length 2 and the other is 3, data in memory will be:

Implementing an array

You can define a $arr function to create an array. The $arr accepts a length argument and returns the beginning offset of the array.

;; create a array
(func $arr (param $len i32) (result i32)
    (local $offset i32)                              ;; offset
    (set_local $offset (i32.load (i32.const 0)))     ;; load offset from the first i32

    (i32.store (get_local $offset)                   ;; load the length
               (get_local $len)
    ) 

    (i32.store (i32.const 0)                         ;; store offset of available space                   
               (i32.add 
                   (i32.add
                       (get_local $offset)
                       (i32.mul 
                           (get_local $len) 
                           (i32.const 4)
                       )
                   )
                   (i32.const 4)                     ;; the first i32 is the length
               )
    )
    (get_local $offset)                              ;; (return) the beginning offset of the array.
)

When creating an array, load the first i32 to $offset, store the array length at $offset, calculate and store the new offset at the first i32. The $offset value is also the result value of the $arr.

The $len function returns the array length.

;; return the array length
(func $len (param $arr i32) (result i32)
    (i32.load (get_local $arr))
)

You pass in the offset of an array. $len loads the i32 value at the offset.

Befor defining array access operators, you can define a helper function to convert an element index to the offset of memory.

;; convert an element index to the offset of memory
(func $offset (param $arr i32) (param $i i32) (result i32)
    (i32.add
         (i32.add (get_local $arr) (i32.const 4))    ;; The first i32 is the array length 
         (i32.mul (i32.const 4) (get_local $i))      ;; one i32 is 4 bytes
    )
)

Then, let's define accessors $set and $get.

;; set a value at the index 
(func $set (param $arr i32) (param $i i32) (param $value i32)
    (i32.store 
        (call $offset (get_local $arr) (get_local $i)) 
        (get_local $value)
    ) 
)
;; get a value at the index 
(func $get (param $arr i32) (param $i i32) (result i32)
    (i32.load 
        (call $offset (get_local $arr) (get_local $i)) 
    )
)

You can create an array now.

(func $main
    (local $a1 i32)

    ;; The first i32 records the beginning offset of available space
    ;; so the initial offset should be 4 (bytes)
    (i32.store (i32.const 0) (i32.const 4))     

    (set_local $a1 (call $arr (i32.const 5)))   ;; create an array with length 0 and assign to $a1

    (call $len (get_local $a1))
    call $log                                   ;; print length 5

    ;; set 10 at the index 1 in $a1
    (call $set (get_local $a1) (i32.const 1) (i32.const 10))

    ;; get 10 at the index 1 
    (call $get (get_local $a1) (i32.const 1))
    call $log                                   ;; print the element value 10
)

When starting $main, you have to store 4 in the first i32. The first i32 records the beginning offset of available space so the initial offset should be 4 (bytes).

The overall module is listed below.

(module
    (import "env" "log" (func $log (param i32)))
    (memory 1)
    ;; create a array
    (func $arr (param $len i32) (result i32)
        (local $offset i32)                              ;; offset
        (set_local $offset (i32.load (i32.const 0)))     ;; load offset from the first i32

        (i32.store (get_local $offset)                   ;; load the length
                   (get_local $len)
        ) 

        (i32.store (i32.const 0)                         ;; store offset of available space                   
                   (i32.add 
                       (i32.add
                           (get_local $offset)
                           (i32.mul 
                               (get_local $len) 
                               (i32.const 4)
                           )
                       )
                       (i32.const 4)                     ;; the first i32 is the length
                   )
        )
        (get_local $offset)                              ;; (return) the beginning offset of the array.
    )
    ;; return the array length
    (func $len (param $arr i32) (result i32)
        (i32.load (get_local $arr))
    )
    ;; convert an element index to the offset of memory
    (func $offset (param $arr i32) (param $i i32) (result i32)
        (i32.add
             (i32.add (get_local $arr) (i32.const 4))    ;; The first i32 is the array length 
             (i32.mul (i32.const 4) (get_local $i))      ;; one i32 is 4 bytes
        )
    )
    ;; set a value at the index 
    (func $set (param $arr i32) (param $i i32) (param $value i32)
        (i32.store 
            (call $offset (get_local $arr) (get_local $i)) 
            (get_local $value)
        ) 
    )
    ;; get a value at the index 
    (func $get (param $arr i32) (param $i i32) (result i32)
        (i32.load 
            (call $offset (get_local $arr) (get_local $i)) 
        )
    )
    (func $main
        (local $a1 i32)

        ;; The first i32 records the beginning offset of available space
        ;; so the initial offset should be 4 (bytes)
        (i32.store (i32.const 0) (i32.const 4))     

        (set_local $a1 (call $arr (i32.const 5)))   ;; create an array with length 0 and assign to $a1

        (call $len (get_local $a1))
        call $log                                   ;; print length 5

        ;; set 10 at the index 1 in $a1
        (call $set (get_local $a1) (i32.const 1) (i32.const 10))

        ;; get 10 at the index 1 
        (call $get (get_local $a1) (i32.const 1))
        call $log                                   ;; print the element value 10
    )
    (start $main)
)

Let's think a question, how to export an array? Remember, The JavaScript representation of memory is actually an ArrayBuffer object. You have to define functions for converting data in ArrayBuffer to JavaScript arrays, such as reading bytes in ArrayBuffer, convert to numbers and adding them to an Array instance.

Importing a JavaScript array to a module requires inversion functions. You read elements from an array, write them to an ArrayBuffer, from WebAssembly.Memory created in JavaScript or an exported WebAssembly.Memory of a module.

Different languages have different high data structures. When supporting WebAssembly, languages should provide their conversion libraries, such as wasm_exec.js in Go 1.11.