Skip to main content

Loader of AssemblyScript WASM modules into Python with Wasmer backend

Project description

What is it

This is loader of AssemblyScript wasm-modules for Python. It use Wasmer for Python as backend. This loader is a direct port of the default AssemblyScript loader from JavaScript to Python. Also we use wasmbind as reference.

How to install

Simply

pip install pyaswasm

Supported features

  • Convert strings and numeric arrays from Python to wasm memory and back
  • Allows to create objects from Python inside wasm memory
  • Create Python wrappers of objects from wasm memory. This allows to call object's methods directly from Python

How to use

The simplest example

# import module
from pyaswasm import ASModule

# instantiate wasm module
module = ASModule("some_module.wasm")

# we can output all functions from the module
print(module.description())
# this will print all global variables, functions and classes, which can be used with this module

# let, for example, out module contains function sqrt_sum_square(float32, float32) -> float32
# this function return sqrt(a^2 + b^2)
# we can use it in the following way
v = module.sqrt_sum_square(3.0, 4.0)
print(v)  # this will output 5.0

How to define custom imports

When the module is instantiated, it may requires some callbacks. For example, wasm module can use external functions. To define it implementation we should use importer dictionary. For example, to implement function console.log(str) in the Python host, ww should write

def console_log(ptr: int):
    print(console_log.module.get_string(ptr))

# ...

module = ASModule("math.some_module", imports={("index", "console.log"): console_log})

Notice that for all functions in the imports dictionary the module add it reference. So, we can use the module inside the function (call console_log.module.get_string in the example).

The module use default functions for abort, trace and seed, but it's possible to override it.

If you would like to define signature of the imported functions, then to the tuple ("module name", "function name") in the imports dictionary you should instead function juxtapose the tuple (function, ([input types], [output types])). For example, for the console_log function it should be

imports={("index", "console.log"): (console_log, ([Type.I32], []))}

How to use arrays

Wasm module return pointers to all non-numeric values in the memory. So, if we use arrays, then it should be created inside the memory and then we should use pointer to the corresponding data block for functions and any other calculations.

from pyaswasm import ASModule

# import wasm module with array functionality
module = ASModule("array.wasm") 

# we would like to generate random float array
float_array_ptr = module.generate_random_float_array(10, -2.0, 2.0)  # this will return pointer to the array
float_array = module.get_array(float_array_ptr)  # convert to Python array

# in the same way we can generate integer array
int_array_ptr = module.generate_random_integer_array(10)
int_array = module.get_array(int_array_ptr)
print(int_array)

# we can change integer array from Python and simultaneously change it values in the memory
# for this purpose we should use method get_array_view
view, start, finish = module.get_array_view(int_array_ptr)  # view is not actual array, but a whole memory, interpreted as integer values
# change the second value
view[start + 1] = 50
# then again get array from the pointer and look at the second element
int_array = module.get_array(int_array_ptr)
print(int_array[1])  # this will output 50

Also we can create array from Python side. But it's important to remember, that all data, creating from Python side, should be pinned in the memory to preserve it destruction from garbage collector.

from pyaswasm import ASModule
import random

module = ASModule("array.wasm") 

# create an array, we should use the method new_array
# also the module should contains global value as a type id for the array, Float32Array_ID in out example
a_ptr = module.new_array(module.Float32Array_ID, [random.uniform(-1.0, 1.0) for i in range(10)])

# next pin the pointer
module.pin(a_ptr)

# then we can make any calculations with this array
# when we does not need this array anymore, unpin it
module.unpin(a_ptr)
# and this will allows to clear the memory by garbage collector

How to use float array view

By default wasmer does not support float view of the memory, but we can use bytes of the array elements.

from pyaswasm import ASModule
import random
import struct

module = ASModule("array.wasm") 

a_ptr = module.new_array(module.Float32Array_ID, [random.uniform(-1.0, 1.0) for i in range(10)])
module.pin(a_ptr)  # we should pin this pointer to preserve it from garbage collector
a = module.get_array(a_ptr)  # variable a contains copy of the array from the memory
a[1] = 16.0
b = module.get_array(a_ptr)
print(a[1] == b[1])  # print False

view, start, finish = module.get_array_view(a_ptr)  # view contains bytes of the array
# we know that array contains f32 values, so, one value corresponds to 4 bytes
# we would like to change the second value array[1] to 17.0
view[start + 4 * 1 : start + 4 * 2] = struct.pack("f", 17.0)
print(module.get_array(a_ptr))  # return the second element = 17.0
module.unpin(a_ptr)

How to use strings

There are two methods for strings: get_string(ptr) -> str convert pointer of the string to Python string, new_string(str) -> int write string to the memory and return pointer to it.

from pyaswasm import ASModule

module = ASModule("string.wasm") 

# get string from the wasm memory
hello_ptr = module.get_hello_string()  # this will return integer pointer to the string
hello_str = module.get_string(hello_ptr)
print(hello_str)  # output Hello world!

# how to create the string
msg_ptr = module.new_string("message string")  # this will create string in the memory and return pointer to it
module.pin(msg_ptr)  # pin it, because we need this string in the memory
newmsg_ptr = module.expand_string(msg_ptr)  # this will return pointer to the new string, generated by module function
newmsg_str = module.get_string(newmsg_ptr)
print(newmsg_str)
module.unpin(msg_ptr)

How to use classes

Classes can be used in two scenarios. The first one is using object from the wasm memory.

from pyaswasm import ASModule

module = ASModule("vector3d.wasm")
# we assume that this module contains class Vector3d and some functions with objects of this class

vectors_ptr = module.get_random_vectors(5)  # return pointer to the array
vectors_array = module.get_array(vectors_ptr)  # this will return array with integer values - pointers to objects
vectors = [module.Vector3d.wrap(v) for v in vectors_array]  # now each element of the array is Python object, which linked with object in the wasm memory
# wrap method automatically pin pointer to the object and unpin it at the end of the object's life cycle
# we can call methods of the objects
# for example, calculate length of each vector
print([v.length() for v in vectors])  # length() is a method in the class inside the module

The second scenario is creating object in the wams memory from Python side. Continue the previous example

new_vector = module.Vector3d(random.uniform(-1.0, 1.0), random.uniform(-1.0, 1.0), random.uniform(-1.0, 1.0))  # we does not need to pin pointer to this object, because it pinned and unpinned automatically
vectors.append(new_vector)
# next we would like to find minimum distance between points (which corresponds to vectors) in the array
# now vectors variable contains Python array, so, we should convert it to the array inside wasm memory
vectors_ptr = module.new_array(module.VectorsArray_ID, vectors)
module.pin(vectors_ptr)
min_distance = module.get_minimum_distance(vectors_ptr)  # this will return float value
print("minimum distance is", min_distance)
module.unpin(vectors_ptr)

Performance

We use benchmark from Path Finder, which contains wasm module for path finding tasks. We use navigation mesh with 2 294 polygons, select different pairs of points and find shortest path between these points in the navigation mesh. The following table contains execution time of different tasks for Node.js (by using default AssemblyScript loader) and Wasmer (by using our loader).

Task Node.js Wasmer
initialization 0.06 sec 0.17 sec
1024 pairs 0.16 sec 0.47 sec
4096 pairs 0.52 sec 1.71 sec
16 384 pairs 2.16 sec 6.94 sec
38 416 pairs 5.19 sec 16.05 sec
65 536 pairs 8.63 sec 27.27 sec

The result: Wasmer is nearly 3.1-3.2 times slowly than Node.js.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

pyaswasm-1.0.2.tar.gz (13.6 kB view hashes)

Uploaded source

Built Distribution

pyaswasm-1.0.2-py3-none-any.whl (10.5 kB view hashes)

Uploaded py3

Supported by

AWS AWS Cloud computing and Security Sponsor Datadog Datadog Monitoring Fastly Fastly CDN Google Google Download Analytics Microsoft Microsoft PSF Sponsor Pingdom Pingdom Monitoring Sentry Sentry Error logging StatusPage StatusPage Status page