Working with Arrays in WebAssembly

Working with Arrays in WebAssembly

Working with arrays in emscripten can be hard for new and even for some experienced developer so lets learn how to do it!!

Introduction

In my previous blog, we have seen how to set up Emscripten and write out the first WebAssembly program. But working with integers is not enough for real-life scenarios so In this blog, we are going to learn how we can pass an array from javascript to C++. So let's start

Writing C++ code

In the code snippet below you can see a function called alter, which takes an array and multiply its value with num.

#include<emscripten.h>
#include<iostream>
extern "C"{
    EMSCRIPTEN_KEEPALIVE
    void alter(long int arr[],int num,int length){
        for(int i = 0 ;i < length;i++){
            arr[i] = arr[i] * num;
        }
    }
}

Here the keyword EMSCRIPTEN_KEEPALIVE is used to tell the compiler to include this function in the output code and extern "C" {} blocks tell the compiler not to change the name of any function i.e; preserve their names.

The most important thing to take note of is that the type of arr which is passed to the function alter should match with the typed array you create on the javascript side, which we will see later in the blog. So for example if we have an Int16Array on the javascript side then we will write short int arr[] in C++ side.

Compiling the code

To compile the code we will use the following command.

em++ array.cpp -o function.js -sEXPORTED_FUNCTIONS=['_malloc','_free']  -sEXPORTED_RUNTIME_METHODS=['ccall'] -sMODULARIZE

The keywords of the command are explained below

  • em++ - triggers the emscripten

  • array.cpp - the name of the C++ file

  • -o function.js - the name of the output file

  • -sEXPORTED_FUNCTIONS=['_malloc','_free'] - tells the compiler to include the specified methods in the output code

  • -sEXPORTED_RUNTIME_METHODS=['ccall'] - tells the compiler to include the specified runtime methods in the output code

  • -sMODULARIZE - tells the compiler to modularize the code so we can get the WASM module as a promise

This command will give us 2 files function.js(contains the necessary code to import and use wasm code) and function.wasm.

Writing JavaScript

So now we have our wasm file ready and we can start writing our javascript code. So first of all create an HTML file and add the following script tag pointing to the function.js file

<script src="./function.js"></script>

Then inside another script tag write the following code

 Module().then((mod) => {
        let array = [1, 2, 3, 4, 5];
        let num = 10;
        let typedArray = new Int32Array(array);
        let pointer = mod._malloc(
          typedArray.length * typedArray.BYTES_PER_ELEMENT
        );
        mod.HEAP32.set(
        typedArray, pointer/typedArray.BYTES_PER_ELEMENT                                                                                   
        );
        mod.ccall(
          "alter",
          null,
          ["number", "number", "number"],
          [pointer, num, typedArray.length]
        );
        let newArray = mod.HEAP32.subarray(
          pointer / typedArray.BYTES_PER_ELEMENT,
          pointer / typedArray.BYTES_PER_ELEMENT + typedArray.length
        );
        console.log(newArray);
      });

Now let's break down the code stepwise and try to understand what's going on.

  1. Module() - this Modulegets resolved to the wasm object which contains all of the methods which we have exported during compilation.

  2. After resolving the Module we have created an array and a number.

  3. Then we convert that array to desired typed array in this case, it is Int16Array

  4. After that, we allocated the memory for the array using _malloc method, which returns the starting address of the allocated memory. _malloc method takes an argument which is the total length of the typed array.

  5. Then we set the typed array in the respective HEAP as the type of the array in this case it is HEAP16 using the set method. set method takes 2 arguments, the first one is the typed array itself and the second one is the offset which is always equal to pointer/typedArray.BYTES_PER_ELEMENT .

  6. After that, we called the alter method using ccall which is exported during the compilation, which takes 4 arguments first one is the name of the function, the second one is the return type, the third one is the types of arguments and the last one is the list of arguments.

  7. And now we are done calling our function and we want the manipulated values which we can be retrieved using the subarray method of the same heap on which we set the typed array. This subarray method takes 2 arguments, the first one is the starting address of the array which is the pointer address returned by the malloc method divided by the byte length of each element of the array and the second one is the ending address which is the pointer address/typedArray.BYTES_PER_ELEMENT + length of the typed array.

  8. Now finally we have our altered array!!.

Output

Thanks for reading the blog, if you have any questions or find some mistakes in the blog comment below👇.