Package: wasmbind
wasmbindWraps your WebAssembly exports to provide you are with a more usable interface in Python.
Currently works withAssemblyScriptmodules, andpython-ext-wasmas the loader.In doing so, it tries to play a similar role aswasm-bindgenoras-bindin JavaScript.Install withpip install wasmbindorpoetry add wasmbind.WARNING: As of this writing, the latest published version 0.3 ofpython-ext-wasmis not supported;
you need to run on git master. The released version does not allow us to access the WASM memory.FeaturesFeatures:✅ Strings, Arrays, Maps, Custom Classes.✅ Work with AssemblyScript objects in Python.✅ Instantiate new AssemblyScript objects in Python.Future plans:Allow wrapping arrays returned from WASM.Improve array allocation by finding available types in RTTI.Support imports (needs#28)Improve these docs.See if we can use RTTI to remove the need for a manualas_. We might have to create a class registry
similar toas-bindInvestigate an alternative approach wherein you predefine classes (with types) in Python code.Allow creation of types without a constructor.UsageSetup your module like this:fromwasmerimportInstancewasm=Instance(open('yourscript.wasm','rb').read())fromwasmbindimportModulemodule=Module(wasm)Here are some sample interactions.Stringsexportfunctionhelloworld(name:string):string{return"hello, "+name}>>>module.helloworld("michael",as_=str)"hello, michael"You'll note that you have to specificy the desired return type viaas_. This is because WASM only
gives us a pointer to a memory location, and we otherwise have no idea what the type is. See the sectionResolving Return Valuesfor other options.Passing valuesintoAssemblyScript works, because we know it the type. In this case, we can allocate
astringon the AssemblyScript side and pass the pointer to it intohelloworld.Note: You'll get a real Pythonstrfrom AssemblyScript, and you are expected to pass realstrobjects to AssemblyScript functions. Strings are immutable in AssemblyScript and Python. Those
things mean that for the boundary Python <-> AssemblyScript, they are passed by value and copied. No
reference counting is involved.Objects & PropertiesexportclassFile{constructor(publicsize:i32,){}}>>>dir=module.Directory(3)>>>dir.size3>>>dir.size=10>>>dir.size10ObjectsexportclassLine{constructor(publics:string){}}exportclassFile{publiclines:Line[]=[]constructor(){}addLine(line:Line):number{this.lines.push(line);returnthis.lines.length;}}>>>file=module.File()>>>line=module.Line("line 1")>>>file.addLine(line)1Maps and other generic typesLet's say you have a function that takes a map as an argument:exportfunctiongetMap():Map<string,i32>{returnnewMap();}First, if you look into this module's exports, you will note that there is onlygetMap(). TheMapclass itself was not exported.Now, if you addexport {Map}, depending on your code, you might see exports such as:'Map<~lib/string/String,~lib/string/String>#get', 'Map<i32,i32>#constructor', 'Map<i32,i32>#clear'Every concrete version of the genericMaptype is exported separately, the names aren't
very nice, and finally, the classes are incomplete: Only methods which were used at some
point in your code are exported, the rest, I assume, have been optimized away.Currently,wasmbinddoes not do anything special with those exports, which means you can
use them, but they are not very accessible.The best way to use a map, which I have found so far, is this:exportclassStringMapextendsMap<string,string>{};This will give you a complete and fully-functionalStringMapclass in Python.Resolving Return ValuesIf you have a memory address, you can do:module.resolve()ormodule.resolve(as_=T)If you have an opaqueAssemblyScriptObject, you can doobj.as_(T).Possible values foras_:If not given, we'll try to auto-detect.strAnyAssemblyScriptClassexported by the module.typing.Listortyping.List[SomeOtherType], withSomeOtherTypebeing anyasvalue.Options for the future:# Every return value is a a Opaque Type that you can either call .native() on or .as().module=Module(instance,value_handler=wrap_opaque)# Every return value is auto-instantiated via the object headermodule=Module(instance,value_handler=auto_resolve)# Using mypy to predefine the return types of each method and function call.module=Module(instance,class_registry={})Opaque ValuesSometimes it can be nice to pass data structures to AssemblyScript that you just want to keep as-is, without
AssemblyScript touching them, and getting them back; in particular, when dealing with complex data structures.To help support this case,wasmbindsupports a mechanism by which:You can put an arbitrary Python value into a local registry.You'll be given an opaque object that you can pass to AssemblyScript functions.AssemblyScript will see an integer (we start counting at 1, so it's up to you if you want to use u8, u32, ...)When a value comes out of AssemblyScript, you need to instructwasmbind, using the regular mechanisms, to
resolve this opaque pointer as awasmbind.OpaqueValueinstance.Here is an example:exportfunctiontake(val:u8):u8{returnval;}fromwasmbindimportOpaqueValuemy_map={"x":1}wrapped_map=module.register_opaque_value(my_map)assertmodule.take(wrapped_map,as_=OpaqueValue)=={"x":1}NotesIn part, this is a port of the AssemblyScript loader. The following links were helpful in implementing this:AssemblyScript Loader codeAssemblyScript Loader docswasmer-as codeas-bind codepython-ext-wasm docs