From fa726d3ac7153d87ed187abd422faa4877f85bb5 Mon Sep 17 00:00:00 2001 From: Kenneth Pouncey Date: Tue, 31 Jul 2018 08:37:11 +0200 Subject: [PATCH] Add support for Blazor custom interop support. (#9713) * Add support for Blazor custom interop support. This adds additional ability to perform unmarshalled calls for advanced but high-performance shared-memory interop. * Note: * This is only safe in Blazor's case because of its exact usage patterns. * Add dotnet_support.js backing file. --- sdks/wasm/Makefile | 9 ++-- sdks/wasm/dotnet_support.js | 105 ++++++++++++++++++++++++++++++++++++++++++++ sdks/wasm/driver.c | 9 ++++ 3 files changed, 119 insertions(+), 4 deletions(-) create mode 100644 sdks/wasm/dotnet_support.js diff --git a/sdks/wasm/Makefile b/sdks/wasm/Makefile index fcc6dda13d8..812cdef0e80 100644 --- a/sdks/wasm/Makefile +++ b/sdks/wasm/Makefile @@ -76,13 +76,13 @@ debug/: release/: mkdir -p $@ -debug/.stamp-build: driver.o library_mono.js binding_support.js $(TOP)/sdks/out/wasm-interp/lib/libmonosgen-2.0.a | debug/ - $(EMCC) -g4 -Os -s WASM=1 -s ALLOW_MEMORY_GROWTH=1 -s BINARYEN=1 -s "BINARYEN_TRAP_MODE='clamp'" -s TOTAL_MEMORY=134217728 -s ALIASING_FUNCTION_POINTERS=0 -s ASSERTIONS=2 --js-library library_mono.js --js-library binding_support.js driver.o $(TOP)/sdks/out/wasm-interp/lib/libmonosgen-2.0.a -o debug/mono.js -s NO_EXIT_RUNTIME=1 -s "EXTRA_EXPORTED_RUNTIME_METHODS=['ccall', 'FS_createPath', 'FS_createDataFile', 'cwrap', 'setValue', 'getValue', 'UTF8ToString']" +debug/.stamp-build: driver.o library_mono.js binding_support.js dotnet_support.js $(TOP)/sdks/out/wasm-interp/lib/libmonosgen-2.0.a | debug/ + $(EMCC) -g4 -Os -s WASM=1 -s ALLOW_MEMORY_GROWTH=1 -s BINARYEN=1 -s "BINARYEN_TRAP_MODE='clamp'" -s TOTAL_MEMORY=134217728 -s ALIASING_FUNCTION_POINTERS=0 -s ASSERTIONS=2 --js-library library_mono.js --js-library binding_support.js --js-library dotnet_support.js driver.o $(TOP)/sdks/out/wasm-interp/lib/libmonosgen-2.0.a -o debug/mono.js -s NO_EXIT_RUNTIME=1 -s "EXTRA_EXPORTED_RUNTIME_METHODS=['ccall', 'FS_createPath', 'FS_createDataFile', 'cwrap', 'setValue', 'getValue', 'UTF8ToString']" touch $@ # Notice that release/.stamp-build depends on debug/.stamp-build. This is the case as emcc is believed to not work well with parallel builds. -release/.stamp-build: driver.o library_mono.js binding_support.js $(TOP)/sdks/out/wasm-interp/lib/libmonosgen-2.0.a debug/.stamp-build | release/ - $(EMCC) -Oz --llvm-opts 2 --llvm-lto 1 -s WASM=1 -s ALLOW_MEMORY_GROWTH=1 -s BINARYEN=1 -s "BINARYEN_TRAP_MODE='clamp'" -s TOTAL_MEMORY=134217728 -s ALIASING_FUNCTION_POINTERS=0 --js-library library_mono.js --js-library binding_support.js driver.o $(TOP)/sdks/out/wasm-interp/lib/libmonosgen-2.0.a -o release/mono.js -s NO_EXIT_RUNTIME=1 -s "EXTRA_EXPORTED_RUNTIME_METHODS=['ccall', 'FS_createPath', 'FS_createDataFile', 'cwrap', 'setValue', 'getValue', 'UTF8ToString']" +release/.stamp-build: driver.o library_mono.js binding_support.js dotnet_support.js $(TOP)/sdks/out/wasm-interp/lib/libmonosgen-2.0.a debug/.stamp-build | release/ + $(EMCC) -Oz --llvm-opts 2 --llvm-lto 1 -s WASM=1 -s ALLOW_MEMORY_GROWTH=1 -s BINARYEN=1 -s "BINARYEN_TRAP_MODE='clamp'" -s TOTAL_MEMORY=134217728 -s ALIASING_FUNCTION_POINTERS=0 --js-library library_mono.js --js-library binding_support.js --js-library dotnet_support.js driver.o $(TOP)/sdks/out/wasm-interp/lib/libmonosgen-2.0.a -o release/mono.js -s NO_EXIT_RUNTIME=1 -s "EXTRA_EXPORTED_RUNTIME_METHODS=['ccall', 'FS_createPath', 'FS_createDataFile', 'cwrap', 'setValue', 'getValue', 'UTF8ToString']" touch $@ mono.js: $(DRIVER_CONF)/.stamp-build @@ -181,6 +181,7 @@ package: build cp $(TOP)/sdks/out/wasm-interp/lib/libmonosgen-2.0.a tmp/ cp library_mono.js tmp/ cp binding_support.js tmp/ + cp dotnet_support.js tmp/ cp -r debug tmp/ cp -r release tmp/ rm tmp/debug/.stamp-build diff --git a/sdks/wasm/dotnet_support.js b/sdks/wasm/dotnet_support.js new file mode 100644 index 00000000000..be94f8766ae --- /dev/null +++ b/sdks/wasm/dotnet_support.js @@ -0,0 +1,105 @@ + +var DotNetSupportLib = { + $DOTNET: { + _dotnet_get_global: function() { + function testGlobal(obj) { + obj['___dotnet_global___'] = obj; + var success = typeof ___dotnet_global___ === 'object' && obj['___dotnet_global___'] === obj; + if (!success) { + delete obj['___dotnet_global___']; + } + return success; + } + if (typeof ___dotnet_global___ === 'object') { + return ___dotnet_global___; + } + if (typeof global === 'object' && testGlobal(global)) { + ___dotnet_global___ = global; + } else if (typeof window === 'object' && testGlobal(window)) { + ___dotnet_global___ = window; + } + if (typeof ___dotnet_global___ === 'object') { + return ___dotnet_global___; + } + throw Error('unable to get DotNet global object.'); + }, + //FIXME this is wastefull, we could remove the temp malloc by going the UTF16 route + //FIXME this is unsafe, cuz raw objects could be GC'd. + conv_string: function (mono_obj) { + if (mono_obj == 0) + return null; + + if (!this.mono_string_get_utf8) + this.mono_string_get_utf8 = Module.cwrap ('mono_wasm_string_get_utf8', 'number', ['number']); + + var raw = this.mono_string_get_utf8 (mono_obj); + var res = Module.UTF8ToString (raw); + Module._free (raw); + + return res; + }, + }, + mono_wasm_invoke_js_marshalled: function(exceptionMessage, asyncHandleLongPtr, functionName, argsJson) { + + var mono_string = DOTNET._dotnet_get_global()._mono_string_cached + || (DOTNET._dotnet_get_global()._mono_string_cached = Module.cwrap('mono_wasm_string_from_js', 'number', ['string'])); + + try { + // Passing a .NET long into JS via Emscripten is tricky. The method here is to pass + // as pointer to the long, then combine two reads from the HEAPU32 array. + // Even though JS numbers can't represent the full range of a .NET long, it's OK + // because we'll never exceed Number.MAX_SAFE_INTEGER (2^53 - 1) in this case. + //var u32Index = $1 >> 2; + var u32Index = asyncHandleLongPtr >> 2; + var asyncHandleJsNumber = Module.HEAPU32[u32Index + 1]*4294967296 + Module.HEAPU32[u32Index]; + + // var funcNameJsString = UTF8ToString (functionName); + // var argsJsonJsString = argsJson && UTF8ToString (argsJson); + var funcNameJsString = DOTNET.conv_string(functionName); + var argsJsonJsString = argsJson && DOTNET.conv_string (argsJson); + + var dotNetExports = DOTNET._dotnet_get_global().DotNet; + if (!dotNetExports) { + throw new Error('The Microsoft.JSInterop.js library is not loaded.'); + } + + if (asyncHandleJsNumber) { + dotNetExports.jsCallDispatcher.beginInvokeJSFromDotNet(asyncHandleJsNumber, funcNameJsString, argsJsonJsString); + return 0; + } else { + var resultJson = dotNetExports.jsCallDispatcher.invokeJSFromDotNet(funcNameJsString, argsJsonJsString); + return resultJson === null ? 0 : mono_string(resultJson); + } + } catch (ex) { + var exceptionJsString = ex.message + '\n' + ex.stack; + var exceptionSystemString = mono_string(exceptionJsString); + setValue (exceptionMessage, exceptionSystemString, 'i32'); // *exceptionMessage = exceptionSystemString; + return 0; + } + }, + mono_wasm_invoke_js_unmarshalled: function(exceptionMessage, funcName, arg0, arg1, arg2) { + try { + // Get the function you're trying to invoke + var funcNameJsString = DOTNET.conv_string(funcName); + var dotNetExports = DOTNET._dotnet_get_global().DotNet; + if (!dotNetExports) { + throw new Error('The Microsoft.JSInterop.js library is not loaded.'); + } + var funcInstance = dotNetExports.jsCallDispatcher.findJSFunction(funcNameJsString); + + return funcInstance.call(null, arg0, arg1, arg2); + } catch (ex) { + var exceptionJsString = ex.message + '\n' + ex.stack; + var mono_string = Module.cwrap('mono_wasm_string_from_js', 'number', ['string']); // TODO: Cache + var exceptionSystemString = mono_string(exceptionJsString); + setValue (exceptionMessage, exceptionSystemString, 'i32'); // *exceptionMessage = exceptionSystemString; + return 0; + } + } + + +}; + +autoAddDeps(DotNetSupportLib, '$DOTNET') +mergeInto(LibraryManager.library, DotNetSupportLib) + diff --git a/sdks/wasm/driver.c b/sdks/wasm/driver.c index 206f1cbeff8..41b8bf835ff 100644 --- a/sdks/wasm/driver.c +++ b/sdks/wasm/driver.c @@ -88,6 +88,10 @@ typedef struct _MonoAssemblyName MonoAssemblyName; //JS funcs extern MonoObject* mono_wasm_invoke_js_with_args (int js_handle, MonoString *method, MonoArray *args, int *is_exception); +// Blazor specific custom routines - see dotnet_support.js for backing code +extern void* mono_wasm_invoke_js_marshalled (MonoString **exceptionMessage, void *asyncHandleLongPtr, MonoString *funcName, MonoString *argsJson); +extern void* mono_wasm_invoke_js_unmarshalled (MonoString **exceptionMessage, MonoString *funcName, void* arg0, void* arg1, void* arg2); + void mono_jit_set_aot_mode (MonoAotMode mode); MonoDomain* mono_jit_init_version (const char *root_domain_name, const char *runtime_version); MonoAssembly* mono_assembly_open (const char *filename, MonoImageOpenStatus *status); @@ -206,6 +210,11 @@ mono_wasm_load_runtime (const char *managed_path, int enable_debugging) mono_add_internal_call ("WebAssembly.Runtime::InvokeJS", mono_wasm_invoke_js); mono_add_internal_call ("WebAssembly.Runtime::InvokeJSWithArgs", mono_wasm_invoke_js_with_args); + + // Blazor specific custom routines - see dotnet_support.js for backing code + mono_add_internal_call ("WebAssembly.JSInterop.InternalCalls::InvokeJSMarshalled", mono_wasm_invoke_js_marshalled); + mono_add_internal_call ("WebAssembly.JSInterop.InternalCalls::InvokeJSUnmarshalled", mono_wasm_invoke_js_unmarshalled); + } EMSCRIPTEN_KEEPALIVE MonoAssembly* -- 2.11.4.GIT