kenemete2
kenemete2

Reputation: 57

Simple method for running JS code stored in Wasm module

From imported function ("Evaluator"), how do I evaluate strings that is stored in WASM memory? String tells to "Evaluator" what to do in browser.

Trivial answer is to import function that takes pointer to memory and calls eval

Trivial "Evaluator" implementation :

 Evaluator: function(ptr) {

    eval(UTF8ToString(ptr));
  },

It requires only 2 methods:

Is there any better method that doesn't use eval?

EDIT: "Evaluator" function should :

String stored in Wasm module memory can be anything.

Upvotes: 2

Views: 1737

Answers (1)

user128511
user128511

Reputation:

I don't think this is the answer you want but it is the answer to the question you asked. So if this is not the answer you wanted then ask a new question

Here's some WebAssembly in WebAssembly text format

(module
  (func $i (import "imports" "imported_func") (param i32))
  (func (export "exported_func")
    i32.const 42
    call $i
  )
)
  • It imports a JavaScript function called "imported_func".

  • It exports a wasm function called "exported_func".

  • "exported_func" calls "imported_func" with 42

To use that it needs to be converted to web assembly binary format. I used the wat2wasm tool. It gave me this data

  0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00, 0x01, 0x08, 0x02, 0x60,
  0x01, 0x7f, 0x00, 0x60, 0x00, 0x00, 0x02, 0x19, 0x01, 0x07, 0x69, 0x6d,
  0x70, 0x6f, 0x72, 0x74, 0x73, 0x0d, 0x69, 0x6d, 0x70, 0x6f, 0x72, 0x74,
  0x65, 0x64, 0x5f, 0x66, 0x75, 0x6e, 0x63, 0x00, 0x00, 0x03, 0x02, 0x01,
  0x01, 0x07, 0x11, 0x01, 0x0d, 0x65, 0x78, 0x70, 0x6f, 0x72, 0x74, 0x65,
  0x64, 0x5f, 0x66, 0x75, 0x6e, 0x63, 0x00, 0x01, 0x0a, 0x08, 0x01, 0x06,
  0x00, 0x41, 0x2a, 0x10, 0x00, 0x0b, 0x00, 0x14, 0x04, 0x6e, 0x61, 0x6d,
  0x65, 0x01, 0x04, 0x01, 0x00, 0x01, 0x69, 0x02, 0x07, 0x02, 0x00, 0x01,
  0x00, 0x00, 0x01, 0x00

To use it normally we'd store the binary version of that data then fetch it but to keep it simple I'll we'll just put it in the JavaScript directly, load it as a wasm module, pass it a function for "imported_func" then call it's "exported_func".

async function main() {
  const wasmModule = new Uint8Array([
    0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00, 0x01, 0x08, 0x02, 0x60,
    0x01, 0x7f, 0x00, 0x60, 0x00, 0x00, 0x02, 0x19, 0x01, 0x07, 0x69, 0x6d,
    0x70, 0x6f, 0x72, 0x74, 0x73, 0x0d, 0x69, 0x6d, 0x70, 0x6f, 0x72, 0x74,
    0x65, 0x64, 0x5f, 0x66, 0x75, 0x6e, 0x63, 0x00, 0x00, 0x03, 0x02, 0x01,
    0x01, 0x07, 0x11, 0x01, 0x0d, 0x65, 0x78, 0x70, 0x6f, 0x72, 0x74, 0x65,
    0x64, 0x5f, 0x66, 0x75, 0x6e, 0x63, 0x00, 0x01, 0x0a, 0x08, 0x01, 0x06,
    0x00, 0x41, 0x2a, 0x10, 0x00, 0x0b, 0x00, 0x14, 0x04, 0x6e, 0x61, 0x6d,
    0x65, 0x01, 0x04, 0x01, 0x00, 0x01, 0x69, 0x02, 0x07, 0x02, 0x00, 0x01,
    0x00, 0x00, 0x01, 0x00,
  ]);

  const importObject = {
    imports: {
      imported_func: (v) => {
         console.log('js called from wasm:', v);
      },
    },
  };

  // create the webassembly
  const {instance} = await WebAssembly.instantiate(wasmModule, importObject);

  // get the web assembly function
  const { exported_func } = instance.exports;

  // call the wasm fucntion
  exported_func();
}
main();

That is WebAssembly. There are compilers that compile other languages that can generate WebAssembly. Emscripten takes C++ in and generates WebAssembly. Rust with some extra tooling can generate WebAssembly. How to call JavaScript from C++ via escripten or how to call JavaScript from Rust would be a different questions. Each of those languages and compilers define their own ways to call JavaScript. That's not how to call JavaScript from WebAssembly though, that' show to call JavaScript from those compilers. The answer above is how to call JavaScript from WebAssembly.

Update

wasm has no knowledge of JavaScript. Nor even its host environment (could be Go, could be Python, etc ...). All it does is you can call functions in a wasm module and you can pass in functions to be called from the wasm module.

Normally if you wanted to have wasm call into the host language you'd write functions in the host language separately so they can be compiled (at load time or compile time)

For languages that can parse and compile at runtime you can embed strings in wasm and then call out to some function you write to take that string and do something with it. One example

   function helperForWasm(str) {
     return eval(str);
   }

Now if you connect the code like the example above you'd get he result of evaluating str

Another example

   const idToJavaScript = {};
   function helperForPreEvaluatingJavaScript(id, js) {
     idToJavaScript[id] = eval(js);
   }
   function helperForCallingFuncById(id, arg1, arg2, arg3) {
     idToJavaScript[id](arg1, arg2, arg);
   }

Now you have 2 functions you can call from wasm. The first evals a js string and assigns the result. If that string is something like

 '(function(v) { return v * 2; })'  // outer parens required

Then after being evaluated using the first function you could call it using the second

You could also do something like (pseudo code)

   function helperForLoadingJavaScriptFromString(js) {
     const blob = new Blob([js], {type: 'application/javascript'});
     const url = URL.createObjectURL(blob);
     import(url).then((jsModule) => {
       // communicate jsModule entry points to wasm
     });
   }

Now call that with full source to JavaScript modules as strings. How you communicate the entries points into the module back to wasm is up to you.

Upvotes: 4

Related Questions