Discord
Login
Community
DARK THEME

How to use WebAssembly in MicroStudio

I will explain the steps to get the initial setup for WebAssembly working. I will also show how to set up shared memory between the .wasm program and microstudio. This will allow us to share arrays between C and microscript.

I found a lot of useful information about webAssembly in this article. It also goes into more depth than I will in terms of memory management. You can also check out the related MDN resources.


Simple function in C

Step 1: writing the function in C

There are several languages that can be compiled to WebAssembly. In this article, I will be using C.

The first step to import a function into MicroStudio is to write it. We will start with a simple add() function:

addition.c

int add(int a, int b) {
  return a + b;
}

Next, we need to compile our addition.c file into addition.wasm.

To do this we will use clang.

clang addition.c \
  --target=wasm32-unknown-unknown-wasm \
  --optimize=3 \
  -nostdlib \
  -Wl,--export-all \
  -Wl,--no-entry \
  -Wl,--allow-undefined \
  --output addition.wasm

If you want to know more about the different flags, they are specified in the article I linked at the top.

Step 2: Importing the function

We now need to create our project in MicroStudio. Click on the (+) icon at the bottom of the left pane and select Assets. We can now drag and drop the addition.wasm file we have just compiled into MicroStudio.

Go in the code editor and import the asset with the asset manager:

init = function()
  
  loader = asset_manager.wasmInstance("addition", function(instance)
    //microscript_fn = instance.export.C_fn
    add = instance.exports.add
    end)
    
end

As you can notice, Microstudio assets do not have file extentions in their name. That means that if we try to import addition.wasm, Microstudio won't find our file. We need to import addition instead.


We can check that our asset has loaded successsfully by calling loader in the console, and from there we can call our C function with add(1, 2).


If all went well you should see

> add(1, 2)
  3

We have just executed wasm code from the MicroStudio console, you can call this function in your code in the same way.


Shared memory

The method shown above will work if the function uses numbers or characters, but if we want to use strings and arrays we need to use shared memory. After importing our wasm instance, we can see by typing loader.instance.exports into the console that we have an object called memory. We will use this object to have a buffer that we can access from both wasm and microstudio.

Let's write a function that returns the sum of two arrays

Here is what the C code looks like:

addArrays.c

void addArraysInt32(int *array1, int *array2, int *result, int length) {
  for (int i = 0; i < length; ++i) {
    result[i] = array1[i] + array2[i];
  }
}

Here we do not have access to memory management in C, so we have passed in a third array called result into which we will store the result.



We need to use javascript to interact with the buffer. We will create a new file with a few functions:

//javascript

//global variables
let offset = 0;
let memory = null;

this.jsStoreMemory = function(mem) {
  memory = mem;
}

this.createArray = function(list) {
  // Create an array that can be passed to the WebAssembly instance.

  let array_length = list.length;
  const array = new Int32Array(memory.buffer, offset, array_length);
  // We offset by the size of the array
  offset += array_length * Int32Array.BYTES_PER_ELEMENT;

  for (let i = 0; i < array_length; i++) {
    array[i] = list[i];  
  }

  return array;
}

And finally, the microscript code to run all this:

init = function()
  loader = asset_manager.wasmInstance("addArrays", function(instance)
    //microscript_fn = instance.exports.C_fn
    addArraysInt32 = instance.exports.addArraysInt32
    memory = instance.exports.memory
    
    // storing the memory object in javascript 
    jsStoreMemory(memory)

    // those arrays can be manipulated from microscript and C
    array1 = createArray([3, 1, 21])
    array2 = createArray([1, 1, 20])
    arrayRes = createArray([0, 0, 0])
    
    
    //to send a pointer to a C function we can use byteOffset
    addArraysInt32(array1.byteOffset,array2.byteOffset, arrayRes.byteOffset, array1.length)
    // the result is stored in arrayRes
    
    // We can directly use the array 
    arrayRes[2] += 1
    
    print(arrayRes)
    
  end)
    

end



Here is what this code does, step by step:

  1. Load the wasm instance
  2. Store the function and the memory
  3. Send the memory to javascript so that we don't need to pass it everytime we call a funtion
  4. Create shared arrays on the buffer
  5. Call a C function and passes it the arrays in the form of pointers
  6. The C function writes the result in one of these arrays
  7. Since the memory is shared, the changes also apply in Microscript

Extending the buffer

Lastly, we'll see how to increase the size of the buffer. By default, our memory weights 128kB. We can increase it by blocks of 64kB.

> print(memory.buffer.byteLength/1024+' kB')
  128 kB
  
> memory.grow(2) // 2 * 64kB
  2
  
> print(memory.buffer.byteLength/1024+' kB')
  256 kB

Keep in mind that every call to grow will detach any references to the old buffer, even for grow(0), as detailed here!

I tried wasm several times - but I always bounced back.

Maybe now it will work!!!

For those who don't look at the Github code every day, has there been an update - WASM support was added 2 days ago.

Post a reply

Progress

Status

Preview
Cancel
Post
Validate your e-mail address to participate in the community