Package: pyaswasm
What is itThis is loader ofAssemblyScriptwasm-modules for Python. It useWasmer for Pythonas backend. This loader is a direct port of the defaultAssemblyScript loaderfrom JavaScript to Python. Also we usewasmbindas reference.How to installSimplypip install pyaswasmSupported featuresConvert strings and numeric arrays from Python to wasm memory and backAllows to create objects from Python inside wasm memoryCreate Python wrappers of objects from wasm memory. This allows to call object's methods directly from PythonHow to useThe simplest example# import modulefrompyaswasmimportASModule# instantiate wasm modulemodule=ASModule("some_module.wasm")# we can output all functions from the moduleprint(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 wayv=module.sqrt_sum_square(3.0,4.0)print(v)# this will output 5.0How to define custom importsWhen 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 functionconsole.log(str)in the Python host, ww should writedefconsole_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 (callconsole_log.module.get_stringin the example).The module use default functions forabort,traceandseed, 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 theconsole_logfunction it should beimports={("index", "console.log"): (console_log, ([Type.I32], []))}How to use arraysWasm 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.frompyaswasmimportASModule# import wasm module with array functionalitymodule=ASModule("array.wasm")# we would like to generate random float arrayfloat_array_ptr=module.generate_random_float_array(10,-2.0,2.0)# this will return pointer to the arrayfloat_array=module.get_array(float_array_ptr)# convert to Python array# in the same way we can generate integer arrayint_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_viewview,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 valueview[start+1]=50# then again get array from the pointer and look at the second elementint_array=module.get_array(int_array_ptr)print(int_array[1])# this will output 50Also 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.frompyaswasmimportASModuleimportrandommodule=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 examplea_ptr=module.new_array(module.Float32Array_ID,[random.uniform(-1.0,1.0)foriinrange(10)])# next pin the pointermodule.pin(a_ptr)# then we can make any calculations with this array# when we does not need this array anymore, unpin itmodule.unpin(a_ptr)# and this will allows to clear the memory by garbage collectorHow to use float array viewBy default wasmer does not support float view of the memory, but we can use bytes of the array elements.frompyaswasmimportASModuleimportrandomimportstructmodule=ASModule("array.wasm")a_ptr=module.new_array(module.Float32Array_ID,[random.uniform(-1.0,1.0)foriinrange(10)])module.pin(a_ptr)# we should pin this pointer to preserve it from garbage collectora=module.get_array(a_ptr)# variable a contains copy of the array from the memorya[1]=16.0b=module.get_array(a_ptr)print(a[1]==b[1])# print Falseview,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.0view[start+4*1:start+4*2]=struct.pack("f",17.0)print(module.get_array(a_ptr))# return the second element = 17.0module.unpin(a_ptr)How to use stringsThere are two methods for strings:get_string(ptr) -> strconvert pointer of the string to Python string,new_string(str) -> intwrite string to the memory and return pointer to it.frompyaswasmimportASModulemodule=ASModule("string.wasm")# get string from the wasm memoryhello_ptr=module.get_hello_string()# this will return integer pointer to the stringhello_str=module.get_string(hello_ptr)print(hello_str)# output Hello world!# how to create the stringmsg_ptr=module.new_string("message string")# this will create string in the memory and return pointer to itmodule.pin(msg_ptr)# pin it, because we need this string in the memorynewmsg_ptr=module.expand_string(msg_ptr)# this will return pointer to the new string, generated by module functionnewmsg_str=module.get_string(newmsg_ptr)print(newmsg_str)module.unpin(msg_ptr)How to use classesClasses can be used in two scenarios. The first one is using object from the wasm memory.frompyaswasmimportASModulemodule=ASModule("vector3d.wasm")# we assume that this module contains class Vector3d and some functions with objects of this classvectors_ptr=module.get_random_vectors(5)# return pointer to the arrayvectors_array=module.get_array(vectors_ptr)# this will return array with integer values - pointers to objectsvectors=[module.Vector3d.wrap(v)forvinvectors_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 vectorprint([v.length()forvinvectors])# length() is a method in the class inside the moduleThe second scenario is creating object in the wams memory from Python side. Continue the previous examplenew_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 automaticallyvectors.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 memoryvectors_ptr=module.new_array(module.VectorsArray_ID,vectors)module.pin(vectors_ptr)min_distance=module.get_minimum_distance(vectors_ptr)# this will return float valueprint("minimum distance is",min_distance)module.unpin(vectors_ptr)PerformanceWe use benchmark fromPath 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).TaskNode.jsWasmerinitialization0.06 sec0.17 sec1024 pairs0.16 sec0.47 sec4096 pairs0.52 sec1.71 sec16 384 pairs2.16 sec6.94 sec38 416 pairs5.19 sec16.05 sec65 536 pairs8.63 sec27.27 secThe result: Wasmer is nearly 3.1-3.2 times slowly than Node.js.