Run wasm built with wasm-pack with pythonmonkey
I have been trying to run wasm code built with wasm-pack with pythonmonkey, a Mozilla SpiderMonkey JavaScript engine embedded into the Python Runtime.
Consider a lib.rs
as follow:
use wasm_bindgen::prelude::*;
#[wasm_bindgen]
pub fn add(a: i32, b: i32) -> i32 {
return a + b;
}
With wasm-pack, we can build the src code to wasm
wasm-pack build --target nodejs --no-typescript
We will get a .wasm file and a .js wrapper file. The case here is that we can not instantiate the .wasm directly, which will get us errors like No module named '__wbindgen_placeholder__'
, or import object field '__wbindgen_init_externref_table' is not a Function
. We should instead import the .js wrapper file, and use exported interfaces.
But import the .js wrapper in pythonmonkey would be a problem, the generated .js wrapper contains the following code:
const path = require('path').join(__dirname, '<your_module_name_bg>.wasm');
const bytes = require('fs').readFileSync(path);
But pythonmonkey doesn’t provide those libs. So we have to monkey patch those modules and methods. For example, a fs
module with readFileSync
method in fs.py
:
def readFileSync(path):
with open(path, 'rb') as fd:
return bytearray(fd.read())
exports = {
'readFileSync': readFileSync
}
We can require fs.py
in pythonmonkey by require('./fs.py')
. And one more step, we have to hack require.cache
to make sure we can require('fs')
in the future code. So we we can write a load function for this:
import os
import pythonmonkey as pm
require = pm.createRequire(os.path.join(os.getcwd(),
f'{__name__}__virtual__'))
pm.globalThis['require'] = require
def load(module, path=None):
if path is None:
path = f'./{module}.py'
pm.eval(f'''
require('{path}')
require.cache['{module}'] = require.cache[require.resolve('{path}')]
''')
Finally, we can run our add(1, 1)
wasm code in pythonmonkey:
load('fs')
load('path')
pm.eval('''
const wasm = require('./pkg/wasm_add.js');
console.log(wasm.add(1, 1));
But if your code is more complex than a simple add
function, especially if you expose more interfaces and more types, the generated .js wrapper file would be much more complex. More modules and functions need to be patched. One more thing, pythonmonkey doesn’t support object destruction(const { a } = {a: 1, b: 2}
) currently(version 1.1.0), so the generated .js wrapper need to be edited.