WebAssembly / binaryen

Optimizer and compiler/toolchain library for WebAssembly
Apache License 2.0
7.39k stars 726 forks source link

[wasm2js] Compiling yew app to asm.js with wasm2js #1781

Open Boscop opened 5 years ago

Boscop commented 5 years ago

I'd like to use the yew frontend framework for the GUI of Windows programs in Rust, in combination with web-view which embeds a IE11 Window. IE11 doesn't support wasm, so I want to compile my frontend.wasm file to asm.js with wasm2js, but what's the right way to do it?

The reason why I don't want to build my frontend to asm.js via the emscripten target is because I don't want the huge emscripten runtime included in my app, and I don't want to depend on emscripten..

Boscop commented 5 years ago

I did the following steps:

wasm2js frontend.wasm -o wasm.jsm

(The frontend.wasm file was generated by cargo web deploy along with a frontend.js file.)

Since it is in ESM format, which IE doesn't support, I transpiled it with babel to UMD so that it works in IE11:

node --max-old-space-size=16384 node_modules\@babel\cli\bin\babel.js --plugins @babel/transform-modules-umd --presets @babel/preset-env wasm.jsm -o wasm.js -s true --module-id meow

Inspired by this.

Then I changed the generated frontend.js file to this: Instead of fetching the wasm file, compiling and instantiating it, and then calling its main() function, since I want to load the wasm.js file, I do that in index.html. But here I first assign all the imports to window so that the wasm.js file can access them, and store instance.initialize with window so that I can call it after wasm.js was loaded:

for (var i in instance.imports) window[i] = instance.imports[i];
window.__initialize = instance.initialize;

The rest of that file stays the same. Now in index.html I put <script src="wasm.js"></script> in the <head> and <script src="frontend-init.js"></script> in the <body>. This file contains the following:

window.__initialize({ exports: window.meow });

When I open index.html in the browser, it loads the frontend UI and also executes the code that connects via websocket to my backend etc. BUT then I get this error:

frontend.js:428 Uncaught TypeError: Cannot read property 'get' of undefined
    at Object.Module.STDWEB_PRIVATE.dyncall (frontend.js:428)
    at WebSocket.output (frontend.js:171)
Module.STDWEB_PRIVATE.dyncall @ frontend.js:428
output @ frontend.js:171
error (async)
(anonymous) @ frontend.js:527
__extjs_7c5535365a3df6a4cc1f59c4a957bfce1dbfb8ee @ frontend.js:527
_$LT$frontend__app__Model$u20$as$u20$yew__html__Component$GT$__update__hc3f28c3839ea6127 @ wasm.jsm:118591
_$LT$yew__html__ComponentEnvelope$LT$COMP$GT$$u20$as$u20$yew__scheduler__Runnable$GT$__run__hc2bdd83f84c2f23f @ wasm.jsm:112824
yew__scheduler__Scheduler__put_and_try_run__h74799cfeb6bb02bb @ wasm.jsm:29774
frontend__main__h4b6651b4f1b2a19d @ wasm.jsm:42438
std__rt__lang_start___$u7b$$u7b$closure$u7d$$u7d$__hc2ce99dbb9f784cb @ wasm.jsm:44454
std__sys_common__backtrace____rust_begin_short_backtrace__hc3417943071d4ca9 @ wasm.jsm:43623
std__rt__lang_start__h60080c07a5a697ad @ wasm.jsm:41592
main @ wasm.jsm:154530
initialize @ frontend.js:747
(anonymous) @ frontend-asm.js:1
frontend.js:428 Uncaught TypeError: Cannot read property 'get' of undefined
    at Object.Module.STDWEB_PRIVATE.dyncall (frontend.js:428)
    at WebSocket.output (frontend.js:171)

Line 428 of frontend.js is the middle line of these 3:

Module.STDWEB_PRIVATE.dyncall = function( signature, ptr, args ) {
    return Module.web_table.get( ptr ).apply( null, args );
};

@yurydelendik helped me on IRC with this and he said that apparently wasm2js doesn't support web table..

What would it take to make this work? :)

Bonus question: I'd prefer to init the js module in the same frontend.js file, like it does it with the wasm file originally. How can I adapt the fetch code so that it fetches and evals the wasm.js file, so that I can then call instance.initialize on it and call its main() function in the same setup js file? So that in my index.html file I only have to include one js file (like in the original wasm case).

yurydelendik commented 5 years ago

Minimal test case:

(module
  (table $t (export "table") 1 anyfunc)
  (elem $t (i32.const 0) $f)
  (func $f (nop))
) 

generates:

function asmFunc(global, env, buffer) {
 "use asm";
 var HEAP8 = new global.Int8Array(buffer);
 var HEAP16 = new global.Int16Array(buffer);
 var HEAP32 = new global.Int32Array(buffer);
 var HEAPU8 = new global.Uint8Array(buffer);
 var HEAPU16 = new global.Uint16Array(buffer);
 var HEAPU32 = new global.Uint32Array(buffer);
 var HEAPF32 = new global.Float32Array(buffer);
 var HEAPF64 = new global.Float64Array(buffer);
 var Math_imul = global.Math.imul;
 var Math_fround = global.Math.fround;
 var Math_abs = global.Math.abs;
 var Math_clz32 = global.Math.clz32;
 var Math_min = global.Math.min;
 var Math_max = global.Math.max;
 var Math_floor = global.Math.floor;
 var Math_ceil = global.Math.ceil;
 var Math_sqrt = global.Math.sqrt;
 var abort = env.abort;
 var nan = global.NaN;
 var infinity = global.Infinity;
 var i64toi32_i32$HIGH_BITS = 0;
 function $0() {

 }

 var FUNCTION_TABLE_v = [$0];
 return {

 };
}

const memasmFunc = new ArrayBuffer(65536);
const retasmFunc = asmFunc({Math,Int8Array,Uint8Array,Int16Array,Uint16Array,Int32Array,Uint32Array,Float32Array,Float64Array,NaN,Infinity}, {abort:function() { throw new Error('abort'); }},memasmFunc);