diff --git a/.gitignore b/.gitignore index 833551119f2d..c1a783db2aa7 100644 --- a/.gitignore +++ b/.gitignore @@ -186,6 +186,7 @@ node_modules/ *.metaproj *.metaproj.tmp bin.localpkg/ +src/mono/wasm/runtime/dotnet.d.ts.sha256 # RIA/Silverlight projects Generated_Code/ diff --git a/eng/liveBuilds.targets b/eng/liveBuilds.targets index 1cd7a1d81a2a..bc2e7e5dff9a 100644 --- a/eng/liveBuilds.targets +++ b/eng/liveBuilds.targets @@ -176,6 +176,8 @@ + + + <_WasmPropertyNames Include="WasmLinkIcalls" /> <_WasmPropertyNames Include="WasmNativeStrip" /> + <_WasmPropertyNames Include="WasmEnableES6" /> <_WasmPropertyNames Include="_WasmDevel" /> <_WasmPropertiesToPass diff --git a/src/installer/pkg/sfx/Microsoft.NETCore.App/Directory.Build.props b/src/installer/pkg/sfx/Microsoft.NETCore.App/Directory.Build.props index 61f2942b48c9..f122fd1fcf52 100644 --- a/src/installer/pkg/sfx/Microsoft.NETCore.App/Directory.Build.props +++ b/src/installer/pkg/sfx/Microsoft.NETCore.App/Directory.Build.props @@ -210,6 +210,7 @@ + @@ -218,10 +219,17 @@ - - - - + + + + + + + + + + + diff --git a/src/mono/sample/mbr/browser/main.js b/src/mono/sample/mbr/browser/main.js index 6294d62187e3..c1ce2f45024c 100644 --- a/src/mono/sample/mbr/browser/main.js +++ b/src/mono/sample/mbr/browser/main.js @@ -7,7 +7,7 @@ var Module = { onConfigLoaded: function () { MONO.config.environment_variables["DOTNET_MODIFIABLE_ASSEMBLIES"] = "debug"; }, - onDotNetReady: function () { + onDotnetReady: function () { App.init(); }, }; diff --git a/src/mono/sample/wasm/Directory.Build.targets b/src/mono/sample/wasm/Directory.Build.targets index 95415ef67fb1..f3ee714290d2 100644 --- a/src/mono/sample/wasm/Directory.Build.targets +++ b/src/mono/sample/wasm/Directory.Build.targets @@ -16,6 +16,9 @@ + + + diff --git a/src/mono/sample/wasm/browser-bench/main.js b/src/mono/sample/wasm/browser-bench/main.js index 40c3e7f67321..c62fb564e218 100644 --- a/src/mono/sample/wasm/browser-bench/main.js +++ b/src/mono/sample/wasm/browser-bench/main.js @@ -4,7 +4,7 @@ "use strict"; var Module = { configSrc: "./mono-config.json", - onDotNetReady: () => { + onDotnetReady: () => { try { App.init(); } catch (error) { diff --git a/src/mono/sample/wasm/browser-es6/Program.cs b/src/mono/sample/wasm/browser-es6/Program.cs new file mode 100644 index 000000000000..743f481896fc --- /dev/null +++ b/src/mono/sample/wasm/browser-es6/Program.cs @@ -0,0 +1,22 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Runtime.CompilerServices; + +namespace Sample +{ + public class Test + { + public static void Main(string[] args) + { + Console.WriteLine ("Hello, World!"); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + public static int TestMeaning() + { + return 42; + } + } +} diff --git a/src/mono/sample/wasm/browser-es6/Wasm.Browser.ES6.Sample.csproj b/src/mono/sample/wasm/browser-es6/Wasm.Browser.ES6.Sample.csproj new file mode 100644 index 000000000000..a617dd85614f --- /dev/null +++ b/src/mono/sample/wasm/browser-es6/Wasm.Browser.ES6.Sample.csproj @@ -0,0 +1,20 @@ + + + Debug + true + main.js + true + embedded + 1 + true + + + + + + + + <_SampleProject>Wasm.Browser.ES6.Sample.csproj + + + diff --git a/src/mono/sample/wasm/browser-es6/index.html b/src/mono/sample/wasm/browser-es6/index.html new file mode 100644 index 000000000000..6dd3faeb7bd7 --- /dev/null +++ b/src/mono/sample/wasm/browser-es6/index.html @@ -0,0 +1,20 @@ + + + + + + + Sample ES6 + + + + + + + + + Answer to the Ultimate Question of Life, the Universe, and Everything is : + + + + \ No newline at end of file diff --git a/src/mono/sample/wasm/browser-es6/main.js b/src/mono/sample/wasm/browser-es6/main.js new file mode 100644 index 000000000000..1cfe1fb3da6a --- /dev/null +++ b/src/mono/sample/wasm/browser-es6/main.js @@ -0,0 +1,21 @@ +import createDotnetRuntime from './dotnet.js' + +const { MONO, BINDING, Module, RuntimeBuildInfo } = await createDotnetRuntime((api) => ({ + disableDotnet6Compatibility: true, + configSrc: "./mono-config.json", + onAbort: () => { + wasm_exit(1); + }, +})); + +function wasm_exit(exit_code) { + console.log(`WASM EXIT ${exit_code}`); +} + +const testMeaning = BINDING.bind_static_method("[Wasm.Browser.ES6.Sample] Sample.Test:TestMeaning"); +const ret = testMeaning(); +document.getElementById("out").innerHTML = `${ret} as computed on dotnet ver ${RuntimeBuildInfo.ProductVersion}`; + +console.debug(`ret: ${ret}`); +let exit_code = ret == 42 ? 0 : 1; +wasm_exit(exit_code); \ No newline at end of file diff --git a/src/mono/sample/wasm/browser-legacy/Program.cs b/src/mono/sample/wasm/browser-legacy/Program.cs new file mode 100644 index 000000000000..743f481896fc --- /dev/null +++ b/src/mono/sample/wasm/browser-legacy/Program.cs @@ -0,0 +1,22 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Runtime.CompilerServices; + +namespace Sample +{ + public class Test + { + public static void Main(string[] args) + { + Console.WriteLine ("Hello, World!"); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + public static int TestMeaning() + { + return 42; + } + } +} diff --git a/src/mono/sample/wasm/browser-legacy/Wasm.Browser.LegacySample.csproj b/src/mono/sample/wasm/browser-legacy/Wasm.Browser.LegacySample.csproj new file mode 100644 index 000000000000..c03897e467d9 --- /dev/null +++ b/src/mono/sample/wasm/browser-legacy/Wasm.Browser.LegacySample.csproj @@ -0,0 +1,21 @@ + + + Debug + true + main.js + true + embedded + 1 + true + + + + + + + + <_SampleProject>Wasm.Browser.LegacySample.csproj + + + + diff --git a/src/mono/sample/wasm/browser-legacy/index.html b/src/mono/sample/wasm/browser-legacy/index.html new file mode 100644 index 000000000000..a2e7322aecf0 --- /dev/null +++ b/src/mono/sample/wasm/browser-legacy/index.html @@ -0,0 +1,44 @@ + + + + + + + Legacy global module sample + + + + + + + Result from Sample.Test.TestMeaning: + + + + + + + + \ No newline at end of file diff --git a/src/mono/sample/wasm/browser-legacy/main.js b/src/mono/sample/wasm/browser-legacy/main.js new file mode 100644 index 000000000000..1aff8d489a12 --- /dev/null +++ b/src/mono/sample/wasm/browser-legacy/main.js @@ -0,0 +1,18 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +"use strict"; +var Module = { + configSrc: "./mono-config.json", + onDotnetReady: () => { + try { + App.init(); + } catch (error) { + set_exit_code(1, error); + throw (error); + } + }, + onAbort: (error) => { + set_exit_code(1, error); + }, +}; diff --git a/src/mono/sample/wasm/browser-profile/main.js b/src/mono/sample/wasm/browser-profile/main.js index 4f9abe18afd6..ed726e4e0264 100644 --- a/src/mono/sample/wasm/browser-profile/main.js +++ b/src/mono/sample/wasm/browser-profile/main.js @@ -12,7 +12,7 @@ var Module = { } } }, - onDotNetReady: () => { + onDotnetReady: () => { try { Module.init(); } catch (error) { diff --git a/src/mono/sample/wasm/browser/index.html b/src/mono/sample/wasm/browser/index.html index 2fa08c756cbf..340b67d645a7 100644 --- a/src/mono/sample/wasm/browser/index.html +++ b/src/mono/sample/wasm/browser/index.html @@ -24,7 +24,7 @@ }; const App = { - init: () => { + init: ({ MONO, BINDING, Module }) => { const testMeaning = BINDING.bind_static_method("[Wasm.Browser.Sample] Sample.Test:TestMeaning"); const ret = testMeaning(); document.getElementById("out").innerHTML = ret; @@ -35,8 +35,8 @@ }, }; + - diff --git a/src/mono/sample/wasm/browser/main.js b/src/mono/sample/wasm/browser/main.js index 9e347d4d346c..0c865b3f0271 100644 --- a/src/mono/sample/wasm/browser/main.js +++ b/src/mono/sample/wasm/browser/main.js @@ -3,11 +3,12 @@ "use strict"; -var Module = { +createDotnetRuntime(({ MONO, BINDING, Module }) => ({ + disableDotnet6Compatibility: true, configSrc: "./mono-config.json", - onDotNetReady: () => { + onDotnetReady: () => { try { - App.init(); + App.init({ MONO, BINDING, Module }); } catch (error) { set_exit_code(1, error); throw (error); @@ -16,4 +17,4 @@ var Module = { onAbort: (error) => { set_exit_code(1, error); }, -}; \ No newline at end of file +})); diff --git a/src/mono/wasm/Makefile b/src/mono/wasm/Makefile index 4152d959e502..04828bba7240 100644 --- a/src/mono/wasm/Makefile +++ b/src/mono/wasm/Makefile @@ -93,7 +93,7 @@ $(NATIVE_BIN_DIR)/include/wasm: $(BUILDS_OBJ_DIR): mkdir -p $$@ -$(NATIVE_BIN_DIR)/dotnet.js: runtime/driver.c runtime/pinvoke.c runtime/pinvoke.h runtime/corebindings.c $(NATIVE_BIN_DIR)/src/runtime.iffe.js runtime/library-dotnet.js $(SYSTEM_NATIVE_LIBDIR)/pal_random.js $(MONO_LIBS) $(EMCC_DEFAULT_RSP) | $(NATIVE_BIN_DIR) +$(NATIVE_BIN_DIR)/dotnet.js: runtime/driver.c runtime/pinvoke.c runtime/pinvoke.h runtime/corebindings.c $(NATIVE_BIN_DIR)/src/cjs/runtime.cjs.iffe.js runtime/cjs/dotnet.cjs.lib.js $(SYSTEM_NATIVE_LIBDIR)/pal_random.lib.js $(MONO_LIBS) $(EMCC_DEFAULT_RSP) | $(NATIVE_BIN_DIR) $(DOTNET) build $(CURDIR)/wasm.proj $(_MSBUILD_WASM_BUILD_ARGS) /t:BuildWasmRuntimes $(MSBUILD_ARGS) $(EMCC_DEFAULT_RSP): $(CURDIR)/wasm.proj | $(NATIVE_BIN_DIR)/src Makefile @@ -113,7 +113,7 @@ clean: icu-files: $(wildcard $(ICU_LIBDIR)/*.dat) $(ICU_LIBDIR)/libicuuc.a $(ICU_LIBDIR)/libicui18n.a | $(NATIVE_BIN_DIR) cp $^ $(NATIVE_BIN_DIR) -source-files: runtime/driver.c runtime/pinvoke.c runtime/corebindings.c runtime/library-dotnet.js $(SYSTEM_NATIVE_LIBDIR)/pal_random.js | $(NATIVE_BIN_DIR)/src +source-files: runtime/driver.c runtime/pinvoke.c runtime/corebindings.c runtime/cjs/dotnet.cjs.lib.js $(SYSTEM_NATIVE_LIBDIR)/pal_random.lib.js | $(NATIVE_BIN_DIR)/src cp $^ $(NATIVE_BIN_DIR)/src header-files: runtime/pinvoke.h | $(NATIVE_BIN_DIR)/include/wasm diff --git a/src/mono/wasm/build/README.md b/src/mono/wasm/build/README.md index 79b9ef30f46d..faed3d05827f 100644 --- a/src/mono/wasm/build/README.md +++ b/src/mono/wasm/build/README.md @@ -32,6 +32,8 @@ Implementation: - *after* any of the wasm build targets, use `AfterTargets="WasmBuildApp"` on that target - Avoid depending on this target, because it is available only when the workload is installed. Use `$(WasmNativeWorkload)` to check if it is installed. +- `WasmEnableES6` will cause native re-link and produce `dotnet.js` as ES6 module. When `Module.disableDotnet6Compatibility` is set it would not pollute global namespace. Currently debugger doesn't work in that pure mode. + ## `Publish` Implementation: diff --git a/src/mono/wasm/build/WasmApp.Native.targets b/src/mono/wasm/build/WasmApp.Native.targets index bfac812dadd9..9fdf3c38aa40 100644 --- a/src/mono/wasm/build/WasmApp.Native.targets +++ b/src/mono/wasm/build/WasmApp.Native.targets @@ -106,6 +106,9 @@ true true + + true + false @@ -114,6 +117,8 @@ true true + + true false @@ -181,6 +186,7 @@ <_EmccCommonFlags Include="$(_DefaultEmccFlags)" /> <_EmccCommonFlags Include="$(EmccFlags)" /> <_EmccCommonFlags Include="-s DISABLE_EXCEPTION_CATCHING=0" /> + <_EmccCommonFlags Include="-s EXPORT_ES6=1" Condition="'$(WasmEnableES6)' == 'true'" /> <_EmccCommonFlags Include="-g" Condition="'$(WasmNativeStrip)' == 'false'" /> <_EmccCommonFlags Include="-v" Condition="'$(EmccVerbose)' != 'false'" /> @@ -222,8 +228,18 @@ <_WasmRuntimePackSrcFile ObjectFile="$(_WasmIntermediateOutputPath)%(FileName).o" /> - <_DotnetJSSrcFile Include="$(_WasmRuntimePackSrcDir)\*.js" Exclude="$(_WasmRuntimePackSrcDir)\*.iffe.js"/> - <_WasmExtraJSFile Include="$(_WasmRuntimePackSrcDir)\*.iffe.js" Kind="extern-pre-js" /> + + + + + + + + + <_WasmExtraJSFile Include="$(_WasmRuntimePackSrcDir)\*.%(JSFileType.Identity)" Kind="%(JSFileType.Kind)" /> + <_WasmExtraJSFile Include="$(_WasmRuntimePackSrcDir)\cjs\*.%(JSFileType.Identity)" Kind="%(JSFileType.Kind)" Condition="'$(WasmEnableES6)' != 'true'" /> + <_WasmExtraJSFile Include="$(_WasmRuntimePackSrcDir)\es6\*.%(JSFileType.Identity)" Kind="%(JSFileType.Kind)" Condition="'$(WasmEnableES6)' == 'true'" /> + <_WasmNativeFileForLinking Include="@(NativeFileReference)" /> @@ -270,7 +286,7 @@ - + @@ -351,14 +367,9 @@ <_WasmExtraJSFile Include="@(Content)" Condition="'%(Content.Extension)' == '.js'" /> <_EmccLinkStepArgs Include="@(_EmccLDFlags)" /> - <_EmccLinkStepArgs Include="--js-library "%(_DotnetJSSrcFile.Identity)"" /> - <_WasmLinkDependencies Include="@(_DotnetJSSrcFile)" /> - <_EmccLinkStepArgs Include="--js-library "%(_WasmExtraJSFile.Identity)"" Condition="'%(_WasmExtraJSFile.Kind)' == 'js-library'" /> - <_EmccLinkStepArgs Include="--pre-js "%(_WasmExtraJSFile.Identity)"" Condition="'%(_WasmExtraJSFile.Kind)' == 'pre-js'" /> - <_EmccLinkStepArgs Include="--extern-pre-js "%(_WasmExtraJSFile.Identity)"" Condition="'%(_WasmExtraJSFile.Kind)' == 'extern-pre-js'" /> - <_EmccLinkStepArgs Include="--post-js "%(_WasmExtraJSFile.Identity)"" Condition="'%(_WasmExtraJSFile.Kind)' == 'post-js'" /> - <_WasmLinkDependencies Include="@(_WasmExtraJSFile)" Condition="'%(_WasmExtraJSFile.Kind)' == 'js-library' or '%(_WasmExtraJSFile.Kind)' == 'pre-js' or '%(_WasmExtraJSFile.Kind)' == 'post-js' or '%(_WasmExtraJSFile.Kind)' == 'extern-post-js'" /> + <_EmccLinkStepArgs Include="--%(_WasmExtraJSFile.Kind) "%(_WasmExtraJSFile.Identity)"" Condition="'%(_WasmExtraJSFile.Kind)' != ''" /> + <_WasmLinkDependencies Include="@(_WasmExtraJSFile)" /> <_EmccLinkStepArgs Include=""%(_WasmNativeFileForLinking.Identity)"" /> <_WasmLinkDependencies Include="@(_WasmNativeFileForLinking)" /> diff --git a/src/mono/wasm/debugger/DebuggerTestSuite/ArrayTests.cs b/src/mono/wasm/debugger/DebuggerTestSuite/ArrayTests.cs index 3d7cdf93ae54..05d3d6cccbf6 100644 --- a/src/mono/wasm/debugger/DebuggerTestSuite/ArrayTests.cs +++ b/src/mono/wasm/debugger/DebuggerTestSuite/ArrayTests.cs @@ -574,7 +574,7 @@ namespace DebuggerTests // Trying to access object as an array if (!DotnetObjectId.TryParse(c_obj_id, out var id) || id.Scheme != "object") - Assert.True(false, "Unexpected object id format. Maybe this test is out of sync with the object id format in library-dotnet.js?"); + Assert.True(false, "Unexpected object id format. Maybe this test is out of sync with the object id format in dotnet.cjs.lib.js?"); if (!int.TryParse(id.Value, out var idNum)) Assert.True(false, "Expected a numeric value part of the object id: {c_obj_id}"); diff --git a/src/mono/wasm/debugger/DebuggerTestSuite/DebuggerTestBase.cs b/src/mono/wasm/debugger/DebuggerTestSuite/DebuggerTestBase.cs index c94697133d53..1329e284eb06 100644 --- a/src/mono/wasm/debugger/DebuggerTestSuite/DebuggerTestBase.cs +++ b/src/mono/wasm/debugger/DebuggerTestSuite/DebuggerTestBase.cs @@ -808,7 +808,7 @@ namespace DebuggerTests return null; var locals = frame_props.Value["result"]; - // FIXME: Should be done when generating the list in library-dotnet.js, but not sure yet + // FIXME: Should be done when generating the list in dotnet.cjs.lib.js, but not sure yet // whether to remove it, and how to do it correctly. if (locals is JArray) { diff --git a/src/mono/wasm/debugger/tests/debugger-test/debugger-main.js b/src/mono/wasm/debugger/tests/debugger-test/debugger-main.js index c34cdf6bae43..8188bdce6854 100644 --- a/src/mono/wasm/debugger/tests/debugger-test/debugger-main.js +++ b/src/mono/wasm/debugger/tests/debugger-test/debugger-main.js @@ -17,7 +17,7 @@ var Module = { }; */ }, - onDotNetReady: () => { + onDotnetReady: () => { App.init(); }, }; diff --git a/src/mono/wasm/runtime/.eslintrc.js b/src/mono/wasm/runtime/.eslintrc.js index 43876b2a90ac..5acfca7fed01 100644 --- a/src/mono/wasm/runtime/.eslintrc.js +++ b/src/mono/wasm/runtime/.eslintrc.js @@ -16,7 +16,12 @@ module.exports = { "plugins": [ "@typescript-eslint" ], - "ignorePatterns": ["node_modules/**/*.*", "bin/**/*.*"], + "ignorePatterns": [ + "node_modules/**/*.*", + "bin/**/*.*", + "cjs/*.js", + "es6/*.js", + ], "rules": { "@typescript-eslint/no-explicit-any": "off", "@typescript-eslint/no-non-null-assertion": "off", diff --git a/src/mono/wasm/runtime/CMakeLists.txt b/src/mono/wasm/runtime/CMakeLists.txt index 4a67bcfe1a81..9721e39682c9 100644 --- a/src/mono/wasm/runtime/CMakeLists.txt +++ b/src/mono/wasm/runtime/CMakeLists.txt @@ -24,8 +24,8 @@ target_link_libraries(dotnet ${NATIVE_BIN_DIR}/libSystem.IO.Compression.Native.a) set_target_properties(dotnet PROPERTIES - LINK_DEPENDS "${NATIVE_BIN_DIR}/src/emcc-default.rsp;${NATIVE_BIN_DIR}/src/runtime.iffe.js;${SOURCE_DIR}/library-dotnet.js;${SYSTEM_NATIVE_DIR}/pal_random.js" - LINK_FLAGS "@${NATIVE_BIN_DIR}/src/emcc-default.rsp ${CONFIGURATION_LINK_FLAGS} -DENABLE_NETCORE=1 --extern-pre-js ${NATIVE_BIN_DIR}/src/runtime.iffe.js --js-library ${SOURCE_DIR}/library-dotnet.js --js-library ${SYSTEM_NATIVE_DIR}/pal_random.js" + LINK_DEPENDS "${NATIVE_BIN_DIR}/src/emcc-default.rsp;${NATIVE_BIN_DIR}/src/cjs/dotnet.cjs.pre.js;${NATIVE_BIN_DIR}/src/cjs/runtime.cjs.iffe.js;${NATIVE_BIN_DIR}/src/cjs/dotnet.cjs.lib.js;${NATIVE_BIN_DIR}/src/pal_random.lib.js;${NATIVE_BIN_DIR}/src/cjs/dotnet.cjs.post.js;${NATIVE_BIN_DIR}/src/cjs/dotnet.cjs.extpost.js;" + LINK_FLAGS "@${NATIVE_BIN_DIR}/src/emcc-default.rsp ${CONFIGURATION_LINK_FLAGS} -DENABLE_NETCORE=1 --extern-pre-js ${NATIVE_BIN_DIR}/src/cjs/runtime.cjs.iffe.js --pre-js ${NATIVE_BIN_DIR}/src/cjs/dotnet.cjs.pre.js --js-library ${NATIVE_BIN_DIR}/src/cjs/dotnet.cjs.lib.js --js-library ${NATIVE_BIN_DIR}/src/pal_random.lib.js --post-js ${NATIVE_BIN_DIR}/src/cjs/dotnet.cjs.post.js --extern-post-js ${NATIVE_BIN_DIR}/src/cjs/dotnet.cjs.extpost.js " RUNTIME_OUTPUT_DIRECTORY "${NATIVE_BIN_DIR}") if(CMAKE_BUILD_TYPE STREQUAL "Release") diff --git a/src/mono/wasm/runtime/buffers.ts b/src/mono/wasm/runtime/buffers.ts index 727ac3e8b590..b0437231ccb8 100644 --- a/src/mono/wasm/runtime/buffers.ts +++ b/src/mono/wasm/runtime/buffers.ts @@ -1,11 +1,12 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -import { Int32Ptr, JSHandle, MonoArray, MonoObject, MonoString, VoidPtr } from "./types"; +import { JSHandle, MonoArray, MonoObject, MonoString } from "./types"; import { Module } from "./imports"; import { mono_wasm_get_jsobj_from_js_handle } from "./gc-handles"; import { wrap_error } from "./method-calls"; import { _js_to_mono_obj } from "./js-to-cs"; +import { Int32Ptr, TypedArray, VoidPtr } from "./types/emscripten"; // Creates a new typed array from pinned array address from pinned_array allocated on the heap to the typed array. // adress of managed pinned array -> copy from heap -> typed array memory diff --git a/src/mono/wasm/runtime/cancelable-promise.ts b/src/mono/wasm/runtime/cancelable-promise.ts index 15da16fc595a..8b749498354d 100644 --- a/src/mono/wasm/runtime/cancelable-promise.ts +++ b/src/mono/wasm/runtime/cancelable-promise.ts @@ -3,7 +3,8 @@ import { mono_wasm_get_jsobj_from_js_handle } from "./gc-handles"; import { wrap_error } from "./method-calls"; -import { Int32Ptr, JSHandle, MonoString } from "./types"; +import { JSHandle, MonoString } from "./types"; +import { Int32Ptr } from "./types/emscripten"; export const _are_promises_supported = ((typeof Promise === "object") || (typeof Promise === "function")) && (typeof Promise.resolve === "function"); const promise_control_symbol = Symbol.for("wasm promise_control"); diff --git a/src/mono/wasm/runtime/cjs/dotnet.cjs.extpost.js b/src/mono/wasm/runtime/cjs/dotnet.cjs.extpost.js new file mode 100644 index 000000000000..7e1523b6d38b --- /dev/null +++ b/src/mono/wasm/runtime/cjs/dotnet.cjs.extpost.js @@ -0,0 +1,4 @@ + +if (typeof globalThis.Module === "object") { + createDotnetRuntime(() => { return globalThis.Module; }).then((exports) => exports); +} \ No newline at end of file diff --git a/src/mono/wasm/runtime/library-dotnet.js b/src/mono/wasm/runtime/cjs/dotnet.cjs.lib.js similarity index 65% rename from src/mono/wasm/runtime/library-dotnet.js rename to src/mono/wasm/runtime/cjs/dotnet.cjs.lib.js index eede40a28f6d..0a76f5f5ca4b 100644 --- a/src/mono/wasm/runtime/library-dotnet.js +++ b/src/mono/wasm/runtime/cjs/dotnet.cjs.lib.js @@ -4,13 +4,20 @@ "use strict"; -const DotNetSupportLib = { +const DotnetSupportLib = { $DOTNET: {}, - $MONO: {}, - $BINDING: {}, - $INTERNAL: {}, - // this line will be executed early on runtime, passing import and export objects into __dotnet_runtime IFFE - $DOTNET__postset: "let api = __dotnet_runtime.__initializeImportsAndExports({isGlobal:true, isNode:ENVIRONMENT_IS_NODE, isShell:ENVIRONMENT_IS_SHELL, isWeb:ENVIRONMENT_IS_WEB, locateFile}, {mono:MONO, binding:BINDING, internal:INTERNAL, module:Module});", + // this line will be placed early on emscripten runtime creation, passing import and export objects into __dotnet_runtime IFFE + $DOTNET__postset: ` + let __dotnet_replacements = {scriptDirectory, readAsync, fetch: globalThis.fetch}; + let __dotnet_exportedAPI = __dotnet_runtime.__initializeImportsAndExports( + { isGlobal:ENVIRONMENT_IS_GLOBAL, isNode:ENVIRONMENT_IS_NODE, isShell:ENVIRONMENT_IS_SHELL, isWeb:ENVIRONMENT_IS_WEB, locateFile }, + { mono:MONO, binding:BINDING, internal:INTERNAL, module:Module }, + __dotnet_replacements); + + // here we replace things which are not exposed in another way + __dirname = scriptDirectory = __dotnet_replacements.scriptDirectory; + readAsync = __dotnet_replacements.readAsync; + var fetch = __dotnet_replacements.fetch;`, }; // the methods would be visible to EMCC linker @@ -65,11 +72,8 @@ const linked_functions = [ // we generate simple proxy for each exported function so that emcc will include them in the final output for (let linked_function of linked_functions) { const fn_template = `return __dotnet_runtime.__linker_exports.${linked_function}.apply(__dotnet_runtime, arguments)`; - DotNetSupportLib[linked_function] = new Function(fn_template); + DotnetSupportLib[linked_function] = new Function(fn_template); } -autoAddDeps(DotNetSupportLib, "$DOTNET"); -autoAddDeps(DotNetSupportLib, "$MONO"); -autoAddDeps(DotNetSupportLib, "$BINDING"); -autoAddDeps(DotNetSupportLib, "$INTERNAL"); -mergeInto(LibraryManager.library, DotNetSupportLib); +autoAddDeps(DotnetSupportLib, "$DOTNET"); +mergeInto(LibraryManager.library, DotnetSupportLib); diff --git a/src/mono/wasm/runtime/cjs/dotnet.cjs.post.js b/src/mono/wasm/runtime/cjs/dotnet.cjs.post.js new file mode 100644 index 000000000000..84493951a5b7 --- /dev/null +++ b/src/mono/wasm/runtime/cjs/dotnet.cjs.post.js @@ -0,0 +1,3 @@ +createDotnetRuntime.ready = createDotnetRuntime.ready.then(() => { + return __dotnet_exportedAPI; +}) diff --git a/src/mono/wasm/runtime/cjs/dotnet.cjs.pre.js b/src/mono/wasm/runtime/cjs/dotnet.cjs.pre.js new file mode 100644 index 000000000000..b98da867a3cb --- /dev/null +++ b/src/mono/wasm/runtime/cjs/dotnet.cjs.pre.js @@ -0,0 +1,23 @@ +const MONO = {}, BINDING = {}, INTERNAL = {}; +let ENVIRONMENT_IS_GLOBAL = typeof globalThis.Module === "object"; +if (ENVIRONMENT_IS_GLOBAL) { + if (globalThis.Module.ready) { + throw new Error("MONO_WASM: Module.ready couldn't be redefined.") + } + globalThis.Module.ready = Module.ready; + Module = createDotnetRuntime = globalThis.Module; +} +else if (typeof createDotnetRuntime === "function") { + ENVIRONMENT_IS_GLOBAL = false; + Module = { ready: Module.ready }; + const extension = createDotnetRuntime({ MONO, BINDING, INTERNAL, Module }) + if (extension.ready) { + throw new Error("MONO_WASM: Module.ready couldn't be redefined.") + } + Object.assign(Module, extension); + createDotnetRuntime = Module; +} +else { + throw new Error("MONO_WASM: Can't locate global Module object or moduleFactory callback of createDotnetRuntime function.") +} +let require = (name) => { return Module.imports.require(name) }; \ No newline at end of file diff --git a/src/mono/wasm/runtime/cs-to-js.ts b/src/mono/wasm/runtime/cs-to-js.ts index a0883815aa83..2a7646d85973 100644 --- a/src/mono/wasm/runtime/cs-to-js.ts +++ b/src/mono/wasm/runtime/cs-to-js.ts @@ -3,7 +3,7 @@ import { mono_wasm_new_root, WasmRoot } from "./roots"; import { - GCHandle, Int32Ptr, JSHandleDisposed, MonoArray, + GCHandle, JSHandleDisposed, MonoArray, MonoArrayNull, MonoObject, MonoObjectNull, MonoString, MonoType, MonoTypeNull } from "./types"; @@ -16,6 +16,7 @@ import { mono_method_get_call_signature, call_method, wrap_error } from "./metho import { _js_to_mono_obj } from "./js-to-cs"; import { _are_promises_supported, _create_cancelable_promise } from "./cancelable-promise"; import { getU32, getI32, getF32, getF64 } from "./memory"; +import { Int32Ptr, VoidPtr } from "./types/emscripten"; // see src/mono/wasm/driver.c MARSHAL_TYPE_xxx and Runtime.cs MarshalType export enum MarshalType { diff --git a/src/mono/wasm/runtime/cwraps.ts b/src/mono/wasm/runtime/cwraps.ts index e991bab9a72c..b8e5ca141e20 100644 --- a/src/mono/wasm/runtime/cwraps.ts +++ b/src/mono/wasm/runtime/cwraps.ts @@ -2,12 +2,12 @@ // The .NET Foundation licenses this file to you under the MIT license. import { - CharPtr, CharPtrPtr, Int32Ptr, MonoArray, MonoAssembly, MonoClass, MonoMethod, MonoObject, MonoString, - MonoType, VoidPtr + MonoType } from "./types"; import { Module } from "./imports"; +import { VoidPtr, CharPtrPtr, Int32Ptr, CharPtr } from "./types/emscripten"; const fn_signatures: [ident: string, returnType: string | null, argTypes?: string[], opts?: any][] = [ // MONO diff --git a/src/mono/wasm/runtime/debug.ts b/src/mono/wasm/runtime/debug.ts index 229003a95477..8eb60b46e547 100644 --- a/src/mono/wasm/runtime/debug.ts +++ b/src/mono/wasm/runtime/debug.ts @@ -4,7 +4,7 @@ import { INTERNAL, Module, MONO, runtimeHelpers } from "./imports"; import { toBase64StringImpl } from "./base64"; import cwraps from "./cwraps"; -import { VoidPtr } from "./types"; +import { VoidPtr, CharPtr } from "./types/emscripten"; let commands_received: CommandResponse; let _call_function_res_cache: any = {}; diff --git a/src/mono/wasm/runtime/dotnet.d.ts b/src/mono/wasm/runtime/dotnet.d.ts new file mode 100644 index 000000000000..cc4341a54dd3 --- /dev/null +++ b/src/mono/wasm/runtime/dotnet.d.ts @@ -0,0 +1,351 @@ +//! Licensed to the .NET Foundation under one or more agreements. +//! The .NET Foundation licenses this file to you under the MIT license. +//! +//! This is generated file, see src/mono/wasm/runtime/rollup.config.js + +declare interface ManagedPointer { + __brandManagedPointer: "ManagedPointer"; +} +declare interface NativePointer { + __brandNativePointer: "NativePointer"; +} +declare interface VoidPtr extends NativePointer { + __brand: "VoidPtr"; +} +declare interface CharPtr extends NativePointer { + __brand: "CharPtr"; +} +declare interface Int32Ptr extends NativePointer { + __brand: "Int32Ptr"; +} +declare interface EmscriptenModule { + HEAP8: Int8Array; + HEAP16: Int16Array; + HEAP32: Int32Array; + HEAPU8: Uint8Array; + HEAPU16: Uint16Array; + HEAPU32: Uint32Array; + HEAPF32: Float32Array; + HEAPF64: Float64Array; + _malloc(size: number): VoidPtr; + _free(ptr: VoidPtr): void; + print(message: string): void; + printErr(message: string): void; + ccall(ident: string, returnType?: string | null, argTypes?: string[], args?: any[], opts?: any): T; + cwrap(ident: string, returnType: string, argTypes?: string[], opts?: any): T; + cwrap(ident: string, ...args: any[]): T; + setValue(ptr: VoidPtr, value: number, type: string, noSafe?: number | boolean): void; + setValue(ptr: Int32Ptr, value: number, type: string, noSafe?: number | boolean): void; + getValue(ptr: number, type: string, noSafe?: number | boolean): number; + UTF8ToString(ptr: CharPtr, maxBytesToRead?: number): string; + UTF8ArrayToString(u8Array: Uint8Array, idx?: number, maxBytesToRead?: number): string; + FS_createPath(parent: string, path: string, canRead?: boolean, canWrite?: boolean): string; + FS_createDataFile(parent: string, name: string, data: TypedArray, canRead: boolean, canWrite: boolean, canOwn?: boolean): string; + removeRunDependency(id: string): void; + addRunDependency(id: string): void; + ready: Promise; + preInit?: (() => any)[]; + preRun?: (() => any)[]; + postRun?: (() => any)[]; + onRuntimeInitialized?: () => any; + instantiateWasm: (imports: any, successCallback: Function) => any; +} +declare type TypedArray = Int8Array | Uint8Array | Uint8ClampedArray | Int16Array | Uint16Array | Int32Array | Uint32Array | Float32Array | Float64Array; + +/** + * Allocates a block of memory that can safely contain pointers into the managed heap. + * The result object has get(index) and set(index, value) methods that can be used to retrieve and store managed pointers. + * Once you are done using the root buffer, you must call its release() method. + * For small numbers of roots, it is preferable to use the mono_wasm_new_root and mono_wasm_new_roots APIs instead. + */ +declare function mono_wasm_new_root_buffer(capacity: number, name?: string): WasmRootBuffer; +/** + * Allocates temporary storage for a pointer into the managed heap. + * Pointers stored here will be visible to the GC, ensuring that the object they point to aren't moved or collected. + * If you already have a managed pointer you can pass it as an argument to initialize the temporary storage. + * The result object has get() and set(value) methods, along with a .value property. + * When you are done using the root you must call its .release() method. + */ +declare function mono_wasm_new_root(value?: T | undefined): WasmRoot; +/** + * Releases 1 or more root or root buffer objects. + * Multiple objects may be passed on the argument list. + * 'undefined' may be passed as an argument so it is safe to call this method from finally blocks + * even if you are not sure all of your roots have been created yet. + * @param {... WasmRoot} roots + */ +declare function mono_wasm_release_roots(...args: WasmRoot[]): void; +declare class WasmRootBuffer { + private __count; + private length; + private __offset; + private __offset32; + private __handle; + private __ownsAllocation; + constructor(offset: VoidPtr, capacity: number, ownsAllocation: boolean, name?: string); + _throw_index_out_of_range(): void; + _check_in_range(index: number): void; + get_address(index: number): NativePointer; + get_address_32(index: number): number; + get(index: number): ManagedPointer; + set(index: number, value: ManagedPointer): ManagedPointer; + _unsafe_get(index: number): number; + _unsafe_set(index: number, value: ManagedPointer | NativePointer): void; + clear(): void; + release(): void; + toString(): string; +} +declare class WasmRoot { + private __buffer; + private __index; + constructor(buffer: WasmRootBuffer, index: number); + get_address(): NativePointer; + get_address_32(): number; + get(): T; + set(value: T): T; + get value(): T; + set value(value: T); + valueOf(): T; + clear(): void; + release(): void; + toString(): string; +} + +declare const enum ArgsMarshal { + Int32 = "i", + Int32Enum = "j", + Int64 = "l", + Int64Enum = "k", + Float32 = "f", + Float64 = "d", + String = "s", + Char = "s", + JSObj = "o", + MONOObj = "m" +} +declare type _ExtraArgsMarshalOperators = "!" | ""; +declare type ArgsMarshalString = "" | `${ArgsMarshal}${_ExtraArgsMarshalOperators}` | `${ArgsMarshal}${ArgsMarshal}${_ExtraArgsMarshalOperators}` | `${ArgsMarshal}${ArgsMarshal}${ArgsMarshal}${_ExtraArgsMarshalOperators}` | `${ArgsMarshal}${ArgsMarshal}${ArgsMarshal}${ArgsMarshal}${_ExtraArgsMarshalOperators}`; + +interface MonoObject extends ManagedPointer { + __brandMonoObject: "MonoObject"; +} +interface MonoString extends MonoObject { + __brand: "MonoString"; +} +interface MonoArray extends MonoObject { + __brand: "MonoArray"; +} +declare type MonoConfig = { + isError: false; + assembly_root: string; + assets: AllAssetEntryTypes[]; + debug_level?: number; + enable_debugging?: number; + globalization_mode: GlobalizationMode; + diagnostic_tracing?: boolean; + remote_sources?: string[]; + environment_variables?: { + [i: string]: string; + }; + runtime_options?: string[]; + aot_profiler_options?: AOTProfilerOptions; + coverage_profiler_options?: CoverageProfilerOptions; + ignore_pdb_load_errors?: boolean; +}; +declare type MonoConfigError = { + isError: true; + message: string; + error: any; +}; +declare type AllAssetEntryTypes = AssetEntry | AssemblyEntry | SatelliteAssemblyEntry | VfsEntry | IcuData; +declare type AssetEntry = { + name: string; + behavior: AssetBehaviours; + virtual_path?: string; + culture?: string; + load_remote?: boolean; + is_optional?: boolean; +}; +interface AssemblyEntry extends AssetEntry { + name: "assembly"; +} +interface SatelliteAssemblyEntry extends AssetEntry { + name: "resource"; + culture: string; +} +interface VfsEntry extends AssetEntry { + name: "vfs"; + virtual_path: string; +} +interface IcuData extends AssetEntry { + name: "icu"; + load_remote: boolean; +} +declare const enum AssetBehaviours { + Resource = "resource", + Assembly = "assembly", + Heap = "heap", + ICU = "icu", + VFS = "vfs" +} +declare const enum GlobalizationMode { + ICU = "icu", + INVARIANT = "invariant", + AUTO = "auto" +} +declare type AOTProfilerOptions = { + write_at?: string; + send_to?: string; +}; +declare type CoverageProfilerOptions = { + write_at?: string; + send_to?: string; +}; +declare type DotnetModuleConfig = { + disableDotnet6Compatibility?: boolean; + config?: MonoConfig | MonoConfigError; + configSrc?: string; + scriptDirectory?: string; + onConfigLoaded?: () => void; + onDotnetReady?: () => void; + imports?: DotnetModuleConfigImports; +} & EmscriptenModule; +declare type DotnetModuleConfigImports = { + require?: (name: string) => any; + fetch?: (url: string) => Promise; + fs?: { + promises?: { + readFile?: (path: string) => Promise; + }; + readFileSync?: (path: string, options: any | undefined) => string; + }; + crypto?: { + randomBytes?: (size: number) => Buffer; + }; + ws?: WebSocket & { + Server: any; + }; + path?: { + normalize?: (path: string) => string; + dirname?: (path: string) => string; + }; + url?: any; +}; + +declare function mono_wasm_runtime_ready(): void; + +declare function mono_wasm_setenv(name: string, value: string): void; +declare function mono_load_runtime_and_bcl_args(config: MonoConfig | MonoConfigError | undefined): Promise; +declare function mono_wasm_load_data_archive(data: Uint8Array, prefix: string): boolean; +/** + * Loads the mono config file (typically called mono-config.json) asynchroniously + * Note: the run dependencies are so emsdk actually awaits it in order. + * + * @param {string} configFilePath - relative path to the config file + * @throws Will throw an error if the config file loading fails + */ +declare function mono_wasm_load_config(configFilePath: string): Promise; + +declare function mono_wasm_load_icu_data(offset: VoidPtr): boolean; + +declare function conv_string(mono_obj: MonoString): string | null; +declare function js_string_to_mono_string(string: string): MonoString | null; + +declare function js_to_mono_obj(js_obj: any): MonoObject; +declare function js_typed_array_to_array(js_obj: any): MonoArray; + +declare function unbox_mono_obj(mono_obj: MonoObject): any; +declare function mono_array_to_js_array(mono_array: MonoArray): any[] | null; + +declare function mono_bind_static_method(fqn: string, signature?: ArgsMarshalString): Function; +declare function mono_call_assembly_entry_point(assembly: string, args: any[], signature: ArgsMarshalString): any; + +declare function mono_wasm_load_bytes_into_heap(bytes: Uint8Array): VoidPtr; + +declare type _MemOffset = number | VoidPtr | NativePointer; +declare function setU8(offset: _MemOffset, value: number): void; +declare function setU16(offset: _MemOffset, value: number): void; +declare function setU32(offset: _MemOffset, value: number): void; +declare function setI8(offset: _MemOffset, value: number): void; +declare function setI16(offset: _MemOffset, value: number): void; +declare function setI32(offset: _MemOffset, value: number): void; +declare function setI64(offset: _MemOffset, value: number): void; +declare function setF32(offset: _MemOffset, value: number): void; +declare function setF64(offset: _MemOffset, value: number): void; +declare function getU8(offset: _MemOffset): number; +declare function getU16(offset: _MemOffset): number; +declare function getU32(offset: _MemOffset): number; +declare function getI8(offset: _MemOffset): number; +declare function getI16(offset: _MemOffset): number; +declare function getI32(offset: _MemOffset): number; +declare function getI64(offset: _MemOffset): number; +declare function getF32(offset: _MemOffset): number; +declare function getF64(offset: _MemOffset): number; + +declare const MONO: { + mono_wasm_setenv: typeof mono_wasm_setenv; + mono_wasm_load_bytes_into_heap: typeof mono_wasm_load_bytes_into_heap; + mono_wasm_load_icu_data: typeof mono_wasm_load_icu_data; + mono_wasm_runtime_ready: typeof mono_wasm_runtime_ready; + mono_wasm_load_data_archive: typeof mono_wasm_load_data_archive; + mono_wasm_load_config: typeof mono_wasm_load_config; + mono_load_runtime_and_bcl_args: typeof mono_load_runtime_and_bcl_args; + mono_wasm_new_root_buffer: typeof mono_wasm_new_root_buffer; + mono_wasm_new_root: typeof mono_wasm_new_root; + mono_wasm_release_roots: typeof mono_wasm_release_roots; + mono_wasm_add_assembly: (name: string, data: VoidPtr, size: number) => number; + mono_wasm_load_runtime: (unused: string, debug_level: number) => void; + config: MonoConfig | MonoConfigError; + loaded_files: string[]; + setI8: typeof setI8; + setI16: typeof setI16; + setI32: typeof setI32; + setI64: typeof setI64; + setU8: typeof setU8; + setU16: typeof setU16; + setU32: typeof setU32; + setF32: typeof setF32; + setF64: typeof setF64; + getI8: typeof getI8; + getI16: typeof getI16; + getI32: typeof getI32; + getI64: typeof getI64; + getU8: typeof getU8; + getU16: typeof getU16; + getU32: typeof getU32; + getF32: typeof getF32; + getF64: typeof getF64; +}; +declare type MONOType = typeof MONO; +declare const BINDING: { + mono_obj_array_new: (size: number) => MonoArray; + mono_obj_array_set: (array: MonoArray, idx: number, obj: MonoObject) => void; + js_string_to_mono_string: typeof js_string_to_mono_string; + js_typed_array_to_array: typeof js_typed_array_to_array; + js_to_mono_obj: typeof js_to_mono_obj; + mono_array_to_js_array: typeof mono_array_to_js_array; + conv_string: typeof conv_string; + bind_static_method: typeof mono_bind_static_method; + call_assembly_entry_point: typeof mono_call_assembly_entry_point; + unbox_mono_obj: typeof unbox_mono_obj; +}; +declare type BINDINGType = typeof BINDING; +interface DotnetPublicAPI { + MONO: typeof MONO; + BINDING: typeof BINDING; + INTERNAL: any; + Module: EmscriptenModule; + RuntimeId: number; + RuntimeBuildInfo: { + ProductVersion: string; + Configuration: string; + }; +} + +declare function createDotnetRuntime(moduleFactory: (api: DotnetPublicAPI) => DotnetModuleConfig): Promise; +declare type CreateDotnetRuntimeType = typeof createDotnetRuntime; +declare global { + function getDotnetRuntime(runtimeId: number): DotnetPublicAPI | undefined; +} + +export { BINDINGType, CreateDotnetRuntimeType, DotnetModuleConfig, DotnetPublicAPI, EmscriptenModule, MONOType, MonoArray, MonoObject, MonoString, VoidPtr, createDotnetRuntime as default }; diff --git a/src/mono/wasm/runtime/es6/dotnet.es6.lib.js b/src/mono/wasm/runtime/es6/dotnet.es6.lib.js new file mode 100644 index 000000000000..341fa01b9078 --- /dev/null +++ b/src/mono/wasm/runtime/es6/dotnet.es6.lib.js @@ -0,0 +1,106 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +/* eslint-disable no-undef */ + +"use strict"; + +const DotnetSupportLib = { + $DOTNET: {}, + // this line will be placed early on emscripten runtime creation, passing import and export objects into __dotnet_runtime IFFE + $DOTNET__postset: ` + let __dotnet_replacements = {scriptDirectory, readAsync, fetch: globalThis.fetch}; + let __dotnet_exportedAPI = __dotnet_runtime.__initializeImportsAndExports( + { isGlobal:ENVIRONMENT_IS_GLOBAL, isNode:ENVIRONMENT_IS_NODE, isShell:ENVIRONMENT_IS_SHELL, isWeb:ENVIRONMENT_IS_WEB, locateFile }, + { mono:MONO, binding:BINDING, internal:INTERNAL, module:Module }, + __dotnet_replacements); + + // here we replace things which are not exposed in another way + __dirname = scriptDirectory = __dotnet_replacements.scriptDirectory; + readAsync = __dotnet_replacements.readAsync; + var fetch = __dotnet_replacements.fetch; + + // here we replace things which are broken on NodeJS for ES6 + if (ENVIRONMENT_IS_NODE) { + getBinaryPromise = async () => { + if (!wasmBinary) { + try { + if (typeof fetch === 'function' && !isFileURI(wasmBinaryFile)) { + const response = await fetch(wasmBinaryFile, { credentials: 'same-origin' }); + if (!response['ok']) { + throw "failed to load wasm binary file at '" + wasmBinaryFile + "'"; + } + return response['arrayBuffer'](); + } + else if (readAsync) { + return await new Promise(function (resolve, reject) { + readAsync(wasmBinaryFile, function (response) { resolve(new Uint8Array(/** @type{!ArrayBuffer} */(response))) }, reject) + }); + } + + } + catch (err) { + return getBinary(wasmBinaryFile); + } + } + return getBinary(wasmBinaryFile); + } + }`, +}; + +// the methods would be visible to EMCC linker +// --- keep in sync with exports.ts --- +const linked_functions = [ + // mini-wasm.c + "mono_set_timeout", + + // mini-wasm-debugger.c + "mono_wasm_asm_loaded", + "mono_wasm_fire_debugger_agent_message", + "mono_wasm_debugger_log", + "mono_wasm_add_dbg_command_received", + + // mono-threads-wasm.c + "schedule_background_exec", + + // driver.c + "mono_wasm_invoke_js", + "mono_wasm_invoke_js_blazor", + "mono_wasm_trace_logger", + + // corebindings.c + "mono_wasm_invoke_js_with_args", + "mono_wasm_get_object_property", + "mono_wasm_set_object_property", + "mono_wasm_get_by_index", + "mono_wasm_set_by_index", + "mono_wasm_get_global_object", + "mono_wasm_create_cs_owned_object", + "mono_wasm_release_cs_owned_object", + "mono_wasm_typed_array_to_array", + "mono_wasm_typed_array_copy_to", + "mono_wasm_typed_array_from", + "mono_wasm_typed_array_copy_from", + "mono_wasm_add_event_listener", + "mono_wasm_remove_event_listener", + "mono_wasm_cancel_promise", + "mono_wasm_web_socket_open", + "mono_wasm_web_socket_send", + "mono_wasm_web_socket_receive", + "mono_wasm_web_socket_close", + "mono_wasm_web_socket_abort", + "mono_wasm_compile_function", + + // pal_icushim_static.c + "mono_wasm_load_icu_data", + "mono_wasm_get_icudt_name", +]; + +// -- this javascript file is evaluated by emcc during compilation! -- +// we generate simple proxy for each exported function so that emcc will include them in the final output +for (let linked_function of linked_functions) { + const fn_template = `return __dotnet_runtime.__linker_exports.${linked_function}.apply(__dotnet_runtime, arguments)`; + DotnetSupportLib[linked_function] = new Function(fn_template); +} + +autoAddDeps(DotnetSupportLib, "$DOTNET"); +mergeInto(LibraryManager.library, DotnetSupportLib); diff --git a/src/mono/wasm/runtime/es6/dotnet.es6.post.js b/src/mono/wasm/runtime/es6/dotnet.es6.post.js new file mode 100644 index 000000000000..841182df7eec --- /dev/null +++ b/src/mono/wasm/runtime/es6/dotnet.es6.post.js @@ -0,0 +1,3 @@ +createDotnetRuntime.ready = createDotnetRuntime.ready.then(() => { + return __dotnet_exportedAPI; +}); \ No newline at end of file diff --git a/src/mono/wasm/runtime/es6/dotnet.es6.pre.js b/src/mono/wasm/runtime/es6/dotnet.es6.pre.js new file mode 100644 index 000000000000..02d8cffb4818 --- /dev/null +++ b/src/mono/wasm/runtime/es6/dotnet.es6.pre.js @@ -0,0 +1,16 @@ +const MONO = {}, BINDING = {}, INTERNAL = {}; +let ENVIRONMENT_IS_GLOBAL = false; +if (typeof createDotnetRuntime === "function") { + Module = { ready: Module.ready }; + const extension = createDotnetRuntime({ MONO, BINDING, INTERNAL, Module }) + if (extension.ready) { + throw new Error("MONO_WASM: Module.ready couldn't be redefined.") + } + Object.assign(Module, extension); + createDotnetRuntime = Module; +} +else { + throw new Error("MONO_WASM: Can't use moduleFactory callback of createDotnetRuntime function.") +} +let require = (name) => { return Module.imports.require(name) }; +var __dirname = ''; \ No newline at end of file diff --git a/src/mono/wasm/runtime/export-types.ts b/src/mono/wasm/runtime/export-types.ts index 281e9fa80aa0..77bca1f53b70 100644 --- a/src/mono/wasm/runtime/export-types.ts +++ b/src/mono/wasm/runtime/export-types.ts @@ -1,18 +1,29 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -import { DotNetPublicAPI } from "./exports"; -import { EmscriptenModuleConfig } from "./types"; +import { BINDINGType, DotnetPublicAPI, MONOType } from "./exports"; +import { DotnetModuleConfig, MonoArray, MonoObject, MonoString } from "./types"; +import { EmscriptenModule, VoidPtr } from "./types/emscripten"; // ----------------------------------------------------------- // this files has all public exports from the dotnet.js module // ----------------------------------------------------------- -declare function createDotnetRuntime(moduleFactory: (api: DotNetPublicAPI) => EmscriptenModuleConfig): Promise; +declare function createDotnetRuntime(moduleFactory: (api: DotnetPublicAPI) => DotnetModuleConfig): Promise; +declare type CreateDotnetRuntimeType = typeof createDotnetRuntime; // Here, declare things that go in the global namespace, or augment existing declarations in the global namespace declare global { - function getDotnetRuntime(runtimeId: number): DotNetPublicAPI | undefined; + function getDotnetRuntime(runtimeId: number): DotnetPublicAPI | undefined; } -export default createDotnetRuntime; \ No newline at end of file +export default createDotnetRuntime; + + +export { + VoidPtr, + MonoObject, MonoString, MonoArray, + BINDINGType, MONOType, EmscriptenModule, + DotnetPublicAPI, DotnetModuleConfig, CreateDotnetRuntimeType +}; + diff --git a/src/mono/wasm/runtime/exports.ts b/src/mono/wasm/runtime/exports.ts index de092d79fb53..f4b44fc9385d 100644 --- a/src/mono/wasm/runtime/exports.ts +++ b/src/mono/wasm/runtime/exports.ts @@ -5,8 +5,8 @@ import ProductVersion from "consts:productVersion"; import Configuration from "consts:configuration"; import { - mono_wasm_new_root, mono_wasm_new_roots, mono_wasm_release_roots, - mono_wasm_new_root_buffer, mono_wasm_new_root_buffer_from_pointer + mono_wasm_new_root, mono_wasm_release_roots, + mono_wasm_new_root_buffer } from "./roots"; import { mono_wasm_send_dbg_command_with_parms, @@ -26,13 +26,14 @@ import { mono_wasm_add_dbg_command_received, } from "./debug"; import { runtimeHelpers, setImportsAndExports } from "./imports"; -import { EmscriptenModuleMono, MonoArray, MonoConfig, MonoConfigError, MonoObject } from "./types"; +import { DotnetModuleConfigImports, DotnetModule } from "./types"; import { mono_load_runtime_and_bcl_args, mono_wasm_load_config, mono_wasm_setenv, mono_wasm_set_runtime_options, mono_wasm_load_data_archive, mono_wasm_asm_loaded, mono_wasm_set_main_args, mono_wasm_pre_init, + mono_wasm_runtime_is_initialized, mono_wasm_on_runtime_initialized } from "./startup"; import { mono_set_timeout, schedule_background_exec } from "./scheduling"; @@ -40,8 +41,7 @@ import { mono_wasm_load_icu_data, mono_wasm_get_icudt_name } from "./icu"; import { conv_string, js_string_to_mono_string, mono_intern_string } from "./strings"; import { js_to_mono_obj, js_typed_array_to_array, mono_wasm_typed_array_to_array } from "./js-to-cs"; import { - mono_array_to_js_array, mono_wasm_create_cs_owned_object, unbox_mono_obj, - _unbox_mono_obj_root_with_known_nonprimitive_type + mono_array_to_js_array, mono_wasm_create_cs_owned_object, unbox_mono_obj } from "./cs-to-js"; import { call_static_method, mono_bind_static_method, mono_call_assembly_entry_point, @@ -50,9 +50,7 @@ import { mono_wasm_get_by_index, mono_wasm_get_global_object, mono_wasm_get_object_property, mono_wasm_invoke_js, mono_wasm_invoke_js_blazor, - mono_wasm_invoke_js_with_args, mono_wasm_set_by_index, mono_wasm_set_object_property, - _get_args_root_buffer_for_method_call, _get_buffer_for_method_call, - _handle_exception_for_call, _teardown_after_call + mono_wasm_invoke_js_with_args, mono_wasm_set_by_index, mono_wasm_set_object_property } from "./method-calls"; import { mono_wasm_typed_array_copy_to, mono_wasm_typed_array_from, mono_wasm_typed_array_copy_from, mono_wasm_load_bytes_into_heap } from "./buffers"; import { mono_wasm_cancel_promise } from "./cancelable-promise"; @@ -68,8 +66,10 @@ import { getU8, getU16, getU32, getF32, getF64, } from "./memory"; import { create_weak_ref } from "./weak-ref"; +import { fetch_like, readAsync_like } from "./polyfills"; +import { EmscriptenModule } from "./types/emscripten"; -const MONO: MONO = { +const MONO = { // current "public" MONO API mono_wasm_setenv, mono_wasm_load_bytes_into_heap, @@ -87,14 +87,31 @@ const MONO: MONO = { mono_wasm_load_runtime: cwraps.mono_wasm_load_runtime, config: runtimeHelpers.config, - loaded_files: [], + loaded_files: [], - // generated bindings closure `library_mono` - mono_wasm_new_root_buffer_from_pointer, - mono_wasm_new_roots, + // memory accessors + setI8, + setI16, + setI32, + setI64, + setU8, + setU16, + setU32, + setF32, + setF64, + getI8, + getI16, + getI32, + getI64, + getU8, + getU16, + getU32, + getF32, + getF64, }; +export type MONOType = typeof MONO; -const BINDING: BINDING = { +const BINDING = { //current "public" BINDING API mono_obj_array_new: cwraps.mono_wasm_obj_array_new, mono_obj_array_set: cwraps.mono_wasm_obj_array_set, @@ -106,19 +123,10 @@ const BINDING: BINDING = { bind_static_method: mono_bind_static_method, call_assembly_entry_point: mono_call_assembly_entry_point, unbox_mono_obj, - - // generated bindings closure `binding_support` - // todo use the methods directly in the closure, not via BINDING - _get_args_root_buffer_for_method_call, - _get_buffer_for_method_call, - invoke_method: cwraps.mono_wasm_invoke_method, - _handle_exception_for_call, - mono_wasm_try_unbox_primitive_and_get_type: cwraps.mono_wasm_try_unbox_primitive_and_get_type, - _unbox_mono_obj_root_with_known_nonprimitive_type, - _teardown_after_call, }; +export type BINDINGType = typeof BINDING; -let api: DotNetPublicAPI; +let exportedAPI: DotnetPublicAPI; // this is executed early during load of emscripten runtime // it exports methods to global objects MONO, BINDING and Module in backward compatible way @@ -126,8 +134,9 @@ let api: DotNetPublicAPI; function initializeImportsAndExports( imports: { isGlobal: boolean, isNode: boolean, isShell: boolean, isWeb: boolean, locateFile: Function }, exports: { mono: any, binding: any, internal: any, module: any }, -): DotNetPublicAPI { - const module = exports.module as EmscriptenModuleMono; + replacements: { scriptDirectory: any, fetch: any, readAsync: any }, +): DotnetPublicAPI { + const module = exports.module as DotnetModule; const globalThisAny = globalThis as any; // we want to have same instance of MONO, BINDING and Module in dotnet iffe @@ -138,7 +147,7 @@ function initializeImportsAndExports( Object.assign(exports.binding, BINDING); Object.assign(exports.internal, INTERNAL); - api = { + exportedAPI = { MONO: exports.mono, BINDING: exports.binding, INTERNAL: exports.internal, @@ -149,28 +158,52 @@ function initializeImportsAndExports( } }; - if (module.configSrc) { - // this could be overriden on Module - if (!module.preInit) { - module.preInit = []; - } else if (typeof module.preInit === "function") { - module.preInit = [module.preInit]; - } - module.preInit.unshift(mono_wasm_pre_init); + // these could be overriden on DotnetModuleConfig + if (!module.preInit) { + module.preInit = []; + } else if (typeof module.preInit === "function") { + module.preInit = [module.preInit]; } - // this could be overriden on Module - if (!module.onRuntimeInitialized) { - module.onRuntimeInitialized = mono_wasm_on_runtime_initialized; + if (!module.preRun) { + module.preRun = []; + } else if (typeof module.preRun === "function") { + module.preRun = [module.preRun]; } + if (!module.print) { module.print = console.log; } if (!module.printErr) { module.printErr = console.error; } + module.imports = module.imports || {}; + if (!module.imports.require) { + module.imports.require = globalThis.require; + } + if (!module.imports.require) { + module.imports.require = (name) => { + const resolve = (module.imports)[name]; + if (!resolve) + throw new Error(`Please provide Module.imports.${name}`); + return resolve; + }; + } - if (imports.isGlobal || !module.disableDotNet6Compatibility) { - Object.assign(module, api); + if (module.imports.fetch) { + runtimeHelpers.fetch = module.imports.fetch; + } + else { + runtimeHelpers.fetch = fetch_like; + } + if (module.scriptDirectory) { + replacements.scriptDirectory = module.scriptDirectory; + } + replacements.fetch = runtimeHelpers.fetch; + replacements.readAsync = readAsync_like; + + // here we expose objects global namespace for tests and backward compatibility + if (imports.isGlobal || !module.disableDotnet6Compatibility) { + Object.assign(module, exportedAPI); // backward compatibility // eslint-disable-next-line @typescript-eslint/ban-ts-comment @@ -180,7 +213,6 @@ function initializeImportsAndExports( return mono_bind_static_method(fqn, signature); }; - // here we expose objects used in tests to global namespace const warnWrap = (name: string, provider: () => any) => { if (typeof globalThisAny[name] !== "undefined") { // it already exists in the global namespace @@ -211,6 +243,35 @@ function initializeImportsAndExports( warnWrap("addRunDependency", () => module.addRunDependency); warnWrap("removeRunDependency", () => module.removeRunDependency); } + + // this is registration of the runtime pre_init, when user set configSrc + if (module.configSrc) { + module.preInit.push(async () => { + module.addRunDependency("mono_wasm_pre_init"); + // execution order == [0] == + await mono_wasm_pre_init(); + module.removeRunDependency("mono_wasm_pre_init"); + }); + } + + // if onRuntimeInitialized is set it's probably Blazor, we let them to do their own init sequence + if (!module.onRuntimeInitialized) { + // note this would keep running in async-parallel with emscripten's `run()` and `postRun()` + // because it's loading files asynchronously and the emscripten is not awaiting onRuntimeInitialized + // execution order == [1] == + module.onRuntimeInitialized = () => mono_wasm_on_runtime_initialized(); + + module.ready = module.ready.then(async () => { + // mono_wasm_runtime_is_initialized is set when finalize_startup is done + await mono_wasm_runtime_is_initialized; + // TODO we could take over Module.postRun and call it from here if necessary + + // execution order == [2] == + return exportedAPI; + }); + } + + // this code makes it possible to find dotnet runtime on a page via global namespace, even when there are multiple runtimes at the same time let list: RuntimeList; if (!globalThisAny.getDotnetRuntime) { globalThisAny.getDotnetRuntime = (runtimeId: string) => globalThisAny.getDotnetRuntime.__list.getRuntime(runtimeId); @@ -219,15 +280,15 @@ function initializeImportsAndExports( else { list = globalThisAny.getDotnetRuntime.__list; } - list.registerRuntime(api); + list.registerRuntime(exportedAPI); - return api; + return exportedAPI; } export const __initializeImportsAndExports: any = initializeImportsAndExports; // don't want to export the type // the methods would be visible to EMCC linker -// --- keep in sync with dotnet.lib.js --- +// --- keep in sync with dotnet.cjs.lib.js --- export const __linker_exports: any = { // mini-wasm.c mono_set_timeout, @@ -305,68 +366,16 @@ const INTERNAL: any = { mono_wasm_detach_debugger, mono_wasm_raise_debug_event, mono_wasm_runtime_is_ready: runtimeHelpers.mono_wasm_runtime_is_ready, - - // memory accessors - setI8, - setI16, - setI32, - setI64, - setU8, - setU16, - setU32, - setF32, - setF64, - getI8, - getI16, - getI32, - getI64, - getU8, - getU16, - getU32, - getF32, - getF64, }; -// this represents visibility in the javascript -// like https://github.com/dotnet/aspnetcore/blob/main/src/Components/Web.JS/src/Platform/Mono/MonoTypes.ts -interface MONO { - mono_wasm_runtime_ready: typeof mono_wasm_runtime_ready - mono_wasm_setenv: typeof mono_wasm_setenv - mono_wasm_load_data_archive: typeof mono_wasm_load_data_archive; - mono_wasm_load_bytes_into_heap: typeof mono_wasm_load_bytes_into_heap; - mono_wasm_load_icu_data: typeof mono_wasm_load_icu_data; - mono_wasm_load_config: typeof mono_wasm_load_config; - mono_load_runtime_and_bcl_args: typeof mono_load_runtime_and_bcl_args; - mono_wasm_new_root_buffer: typeof mono_wasm_new_root_buffer; - mono_wasm_new_root: typeof mono_wasm_new_root; - mono_wasm_release_roots: typeof mono_wasm_release_roots; - - // for Blazor's future! - mono_wasm_add_assembly: (name: string, data: VoidPtr, size: number) => number, - mono_wasm_load_runtime: (unused: string, debug_level: number) => void, - - loaded_files: string[]; - config: MonoConfig | MonoConfigError, -} // this represents visibility in the javascript // like https://github.com/dotnet/aspnetcore/blob/main/src/Components/Web.JS/src/Platform/Mono/MonoTypes.ts -interface BINDING { - mono_obj_array_new: (size: number) => MonoArray, - mono_obj_array_set: (array: MonoArray, idx: number, obj: MonoObject) => void, - js_string_to_mono_string: typeof js_string_to_mono_string, - js_typed_array_to_array: typeof js_typed_array_to_array, - js_to_mono_obj: typeof js_to_mono_obj, - mono_array_to_js_array: typeof mono_array_to_js_array, - conv_string: typeof conv_string, - bind_static_method: typeof mono_bind_static_method, - call_assembly_entry_point: typeof mono_call_assembly_entry_point, - unbox_mono_obj: typeof unbox_mono_obj -} -export interface DotNetPublicAPI { - MONO: MONO, - BINDING: BINDING, - Module: any, +export interface DotnetPublicAPI { + MONO: typeof MONO, + BINDING: typeof BINDING, + INTERNAL: any, + Module: EmscriptenModule, RuntimeId: number, RuntimeBuildInfo: { ProductVersion: string, @@ -375,15 +384,15 @@ export interface DotNetPublicAPI { } class RuntimeList { - private list: { [runtimeId: number]: WeakRef } = {}; + private list: { [runtimeId: number]: WeakRef } = {}; - public registerRuntime(api: DotNetPublicAPI): number { + public registerRuntime(api: DotnetPublicAPI): number { api.RuntimeId = Object.keys(this.list).length; this.list[api.RuntimeId] = create_weak_ref(api); return api.RuntimeId; } - public getRuntime(runtimeId: number): DotNetPublicAPI | undefined { + public getRuntime(runtimeId: number): DotnetPublicAPI | undefined { const wr = this.list[runtimeId]; return wr ? wr.deref() : undefined; } diff --git a/src/mono/wasm/runtime/icu.ts b/src/mono/wasm/runtime/icu.ts index a2c247d9a9cb..58ad13628cee 100644 --- a/src/mono/wasm/runtime/icu.ts +++ b/src/mono/wasm/runtime/icu.ts @@ -2,7 +2,8 @@ // The .NET Foundation licenses this file to you under the MIT license. import cwraps from "./cwraps"; -import { GlobalizationMode, VoidPtr } from "./types"; +import { GlobalizationMode } from "./types"; +import { VoidPtr } from "./types/emscripten"; let num_icu_assets_loaded_successfully = 0; @@ -27,7 +28,7 @@ export function mono_wasm_get_icudt_name(culture: string): string { // @globalization_mode is one of "icu", "invariant", or "auto". // "auto" will use "icu" if any ICU data archives have been loaded, // otherwise "invariant". -export function mono_wasm_globalization_init(globalization_mode: GlobalizationMode): void { +export function mono_wasm_globalization_init(globalization_mode: GlobalizationMode, tracing: boolean): void { let invariantMode = false; if (globalization_mode === "invariant") @@ -35,9 +36,13 @@ export function mono_wasm_globalization_init(globalization_mode: GlobalizationMo if (!invariantMode) { if (num_icu_assets_loaded_successfully > 0) { - console.debug("MONO_WASM: ICU data archive(s) loaded, disabling invariant mode"); + if (tracing) { + console.debug("MONO_WASM: ICU data archive(s) loaded, disabling invariant mode"); + } } else if (globalization_mode !== "icu") { - console.debug("MONO_WASM: ICU data archive(s) not loaded, using invariant globalization mode"); + if (tracing) { + console.debug("MONO_WASM: ICU data archive(s) not loaded, using invariant globalization mode"); + } invariantMode = true; } else { const msg = "invariant globalization mode is inactive and no ICU data archives were loaded"; diff --git a/src/mono/wasm/runtime/imports.ts b/src/mono/wasm/runtime/imports.ts index be89b335731c..7ebe3a5c09bb 100644 --- a/src/mono/wasm/runtime/imports.ts +++ b/src/mono/wasm/runtime/imports.ts @@ -2,13 +2,13 @@ // The .NET Foundation licenses this file to you under the MIT license. /* eslint-disable @typescript-eslint/triple-slash-reference */ -/// /// -import { EmscriptenModuleMono, MonoConfig, RuntimeHelpers } from "./types"; +import { DotnetModule, MonoConfig, RuntimeHelpers } from "./types"; +import { EmscriptenModule } from "./types/emscripten"; // these are our public API (except internal) -export let Module: EmscriptenModule & EmscriptenModuleMono; +export let Module: EmscriptenModule & DotnetModule; export let MONO: any; export let BINDING: any; export let INTERNAL: any; @@ -24,7 +24,7 @@ export let locateFile: Function; export function setImportsAndExports( imports: { isGlobal: boolean, isNode: boolean, isShell: boolean, isWeb: boolean, locateFile: Function }, exports: { mono: any, binding: any, internal: any, module: any }, -) { +): void { MONO = exports.mono; BINDING = exports.binding; INTERNAL = exports.internal; @@ -57,4 +57,5 @@ export const runtimeHelpers: RuntimeHelpers = { MONO.config = value; Module.config = value; }, + fetch: null }; diff --git a/src/mono/wasm/runtime/js-to-cs.ts b/src/mono/wasm/runtime/js-to-cs.ts index 01be716a7b70..8bafaec669cd 100644 --- a/src/mono/wasm/runtime/js-to-cs.ts +++ b/src/mono/wasm/runtime/js-to-cs.ts @@ -14,8 +14,9 @@ import { wrap_error } from "./method-calls"; import { js_string_to_mono_string, js_string_to_mono_string_interned } from "./strings"; import { isThenable } from "./cancelable-promise"; import { has_backing_array_buffer } from "./buffers"; -import { Int32Ptr, JSHandle, MonoArray, MonoMethod, MonoObject, MonoObjectNull, MonoString, wasm_type_symbol } from "./types"; +import { JSHandle, MonoArray, MonoMethod, MonoObject, MonoObjectNull, MonoString, wasm_type_symbol } from "./types"; import { setI32, setU32, setF64 } from "./memory"; +import { Int32Ptr, TypedArray } from "./types/emscripten"; // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types export function _js_to_mono_uri(should_add_in_flight: boolean, js_obj: any): MonoObject { diff --git a/src/mono/wasm/runtime/memory.ts b/src/mono/wasm/runtime/memory.ts index 55a322637b90..e8a89bd11a57 100644 --- a/src/mono/wasm/runtime/memory.ts +++ b/src/mono/wasm/runtime/memory.ts @@ -1,4 +1,5 @@ import { Module } from "./imports"; +import { VoidPtr, NativePointer } from "./types/emscripten"; const _temp_mallocs: Array | null> = []; diff --git a/src/mono/wasm/runtime/method-binding.ts b/src/mono/wasm/runtime/method-binding.ts index 1cd070ae944a..9b4adbd2b654 100644 --- a/src/mono/wasm/runtime/method-binding.ts +++ b/src/mono/wasm/runtime/method-binding.ts @@ -2,7 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. import { WasmRoot, WasmRootBuffer, mono_wasm_new_root } from "./roots"; -import { MonoClass, MonoMethod, MonoObject, coerceNull, VoidPtrNull, VoidPtr, MonoType } from "./types"; +import { MonoClass, MonoMethod, MonoObject, coerceNull, VoidPtrNull, MonoType } from "./types"; import { BINDING, Module, runtimeHelpers } from "./imports"; import { js_to_mono_enum, _js_to_mono_obj, _js_to_mono_uri } from "./js-to-cs"; import { js_string_to_mono_string, js_string_to_mono_string_interned } from "./strings"; @@ -17,6 +17,7 @@ import { _handle_exception_for_call, _teardown_after_call } from "./method-calls"; import cwraps from "./cwraps"; +import { VoidPtr } from "./types/emscripten"; const primitiveConverters = new Map(); const _signature_converters = new Map(); diff --git a/src/mono/wasm/runtime/method-calls.ts b/src/mono/wasm/runtime/method-calls.ts index 8d7174e8b488..64d4d06eb8dc 100644 --- a/src/mono/wasm/runtime/method-calls.ts +++ b/src/mono/wasm/runtime/method-calls.ts @@ -5,7 +5,7 @@ import { mono_wasm_new_root, mono_wasm_new_root_buffer, WasmRoot, WasmRootBuffer import { JSHandle, MonoArray, MonoMethod, MonoObject, MonoObjectNull, MonoString, coerceNull as coerceNull, - VoidPtr, VoidPtrNull, Int32Ptr, MonoStringNull + VoidPtrNull, MonoStringNull } from "./types"; import { BINDING, INTERNAL, Module, MONO, runtimeHelpers } from "./imports"; import { _mono_array_root_to_js_array, _unbox_mono_obj_root } from "./cs-to-js"; @@ -21,6 +21,7 @@ import { conv_string, js_string_to_mono_string } from "./strings"; import cwraps from "./cwraps"; import { bindings_lazy_init } from "./startup"; import { _create_temp_frame, _release_temp_frame } from "./memory"; +import { VoidPtr, Int32Ptr, EmscriptenModule } from "./types/emscripten"; function _verify_args_for_method_call(args_marshal: ArgsMarshalString, args: any) { const has_args = args && (typeof args === "object") && args.length > 0; @@ -249,7 +250,7 @@ export function call_static_method(fqn: string, args: any[], signature: ArgsMars return call_method(method, undefined, signature, args); } -export function mono_bind_static_method(fqn: string, signature: ArgsMarshalString): Function { +export function mono_bind_static_method(fqn: string, signature?: ArgsMarshalString): Function { bindings_lazy_init();// TODO remove this once Blazor does better startup const method = mono_method_resolve(fqn); diff --git a/src/mono/wasm/runtime/modularize-dotnet.md b/src/mono/wasm/runtime/modularize-dotnet.md new file mode 100644 index 000000000000..0ec846fa7592 --- /dev/null +++ b/src/mono/wasm/runtime/modularize-dotnet.md @@ -0,0 +1,94 @@ +# Linked javascript files +They are emcc way how to extend the dotnet.js script during linking, by appending the scripts. +See https://emscripten.org/docs/tools_reference/emcc.html#emcc-pre-js + +There are `-extern-pre-js`,`-pre-js`, `-post-js`, `-extern-post-js`. +In `src\mono\wasm\build\WasmApp.Native.targets` we apply them by file naming convention as: `*.extpre.js`,`*.pre.js`, `*.post.js`, `*.extpost.js` +- For ES6 with `WasmEnableES6 == true` from `src/es6`folder +- For CommonJS with `WasmEnableES6 == false` from `src/cjs`folder + +In `src\mono\wasm\runtime\CMakeLists.txt` which links only in-tree, we use same mapping explicitly. Right now CommonJS is default. + +# dotnet.cjs.extpost.js +- Is at the end of file but is executed first (1) +- Applied only when linking CommonJS +- If `globalThis.Module` exist it takes it and start runtime with it. +- Otherwise user could still use the `createDotnetRuntime` export or `globalThis.createDotnetRuntime` if it was loaded into global namespace. + +# dotnet.cjs.pre.js +- Executed second (2) +- Applied only when linking CommonJS +- Will try to see if it was executed with `globalThis.Module` and if so, it would use it's instance as `Module`. It would preserve emscripten's `Module.ready` +- Otherwise it would load it would assume it was called via `createDotnetRuntime` export same as described for `dotnet.es6.pre.js` below. + +# dotnet.es6.pre.js +- Executed second (2) +- Applied only when linking ES6 +- Will check that it was passed `moduleFactory` callback. Because of emscripten reasons it has confusing `createDotnetRuntime` name here. +- Will validate `Module.ready` is left un-overriden. + +# runtime.*.iffe.js +- Executed third (3) +- this is produced from `*.ts` files in this directory by rollupJS. + +# dotnet.*.post.js +- Executed last (4) +- When `onRuntimeInitialized` is overriden it would wait for emscriptens `Module.ready` +- Otherwise it would wait for for MonoVM to load all assets and assemblies. +- It would pass on the API exports + +# About new API +The signature is +``` +function createDotnetRuntime(moduleFactory: (api: DotnetPublicAPI) => DotnetModuleConfig): Promise +``` + +Simplest intended usage looks like this in ES6: +``` +import createDotnetRuntime from './dotnet.js' + +await createDotnetRuntime(() => ({ + configSrc: "./mono-config.json", +})); +``` + +More complex scenario with using APIs, commented +``` +import createDotnetRuntime from './dotnet.js' + +export const { MONO, BINDING } = await createDotnetRuntime(({ MONO, BINDING, Module }) => +// this is callback with no statement, the APIs are only empty shells here and are populated later. +({ + disableDotnet6Compatibility: true, + configSrc: "./mono-config.json", + onConfigLoaded: () => { + // This is called during emscripten `preInit` event, after we fetched config. + + // Module.config is loaded and could be tweaked before application + Module.config.environment_variables["MONO_LOG_LEVEL"]="debug" + + // here we could use API passed into this callback + // call some early available functions + MONO.mono_wasm_setenv("HELLO", "WORLD); + } + onDotnetReady: () => { + // Only when there is no `onRuntimeInitialized` override. + // This is called after all assets are loaded , mapping to legacy `config.loaded_cb`. + // It happens during emscripten `onRuntimeInitialized` after monoVm init + globalization + assemblies. + // This also matches when the top level promise is resolved. + // The original emscripten `Module.ready` promise is replaced with this. + + // at this point both emscripten and monoVM are fully initialized. + Module.FS.chdir(processedArguments.working_dir); + }, + onAbort: (error) => { + set_exit_code(1, error); + }, +})); + +// at this point both emscripten and monoVM are fully initialized. +// we could use the APIs returned and resolved from createDotnetRuntime promise +// both API exports are receiving the same API object instances, i.e. same `MONO` instance. +const run_all = BINDING.bind_static_method ("[debugger-test] DebuggerTest:run_all"); +run_all(); +``` diff --git a/src/mono/wasm/runtime/package.json b/src/mono/wasm/runtime/package.json index b332bf1141e0..fe915ac812cf 100644 --- a/src/mono/wasm/runtime/package.json +++ b/src/mono/wasm/runtime/package.json @@ -6,6 +6,8 @@ "url": "git@github.com:dotnet/runtime.git" }, "version": "1.0.0", + "main": "dotnet.js", + "types": "dotnet.d.ts", "scripts": { "rollup": "rollup -c", "lint": "eslint --no-color --max-warnings=0 ./**/*.ts ./*.js" diff --git a/src/mono/wasm/runtime/polyfills.ts b/src/mono/wasm/runtime/polyfills.ts new file mode 100644 index 000000000000..01d4d2ded9bc --- /dev/null +++ b/src/mono/wasm/runtime/polyfills.ts @@ -0,0 +1,50 @@ +import { ENVIRONMENT_IS_NODE, Module } from "./imports"; + +export async function fetch_like(url: string): Promise { + try { + if (typeof (globalThis.fetch) === "function") { + return globalThis.fetch(url, { credentials: "same-origin" }); + } + else if (ENVIRONMENT_IS_NODE) { + const node_fs = Module.imports!.require!("fs"); + const node_url = Module.imports!.require!("url"); + if (url.startsWith("file://")) { + url = node_url.fileURLToPath(url); + } + + const arrayBuffer = await node_fs.promises.readFile(url); + return { + ok: true, + url, + arrayBuffer: () => arrayBuffer, + json: () => JSON.parse(arrayBuffer) + }; + } + else if (typeof (read) === "function") { + const arrayBuffer = new Uint8Array(read(url, "binary")); + return { + ok: true, + url, + arrayBuffer: () => arrayBuffer, + json: () => JSON.parse(Module.UTF8ArrayToString(arrayBuffer, 0, arrayBuffer.length)) + }; + } + } + catch (e: any) { + return { + ok: false, + url, + arrayBuffer: () => { throw e; }, + json: () => { throw e; } + }; + } + throw new Error("No fetch implementation available"); +} + +export function readAsync_like(url: string, onload: Function, onerror: Function): void { + fetch_like(url).then((res: Response) => { + onload(res.arrayBuffer()); + }).catch((err) => { + onerror(err); + }); +} \ No newline at end of file diff --git a/src/mono/wasm/runtime/rollup.config.js b/src/mono/wasm/runtime/rollup.config.js index f3cbc55bda9e..b9266366507e 100644 --- a/src/mono/wasm/runtime/rollup.config.js +++ b/src/mono/wasm/runtime/rollup.config.js @@ -3,11 +3,11 @@ import typescript from "@rollup/plugin-typescript"; import { terser } from "rollup-plugin-terser"; import { readFile, writeFile, mkdir } from "fs/promises"; import * as fs from "fs"; +import * as path from "path"; import { createHash } from "crypto"; import dts from "rollup-plugin-dts"; import consts from "rollup-plugin-consts"; -const outputFileName = "runtime.iffe.js"; const configuration = process.env.Configuration; const isDebug = configuration !== "Release"; const productVersion = process.env.ProductVersion || "7.0.0-dev"; @@ -40,34 +40,59 @@ const terserConfig = { }; const plugins = isDebug ? [writeOnChangePlugin()] : [terser(terserConfig), writeOnChangePlugin()]; const banner = "//! Licensed to the .NET Foundation under one or more agreements.\n//! The .NET Foundation licenses this file to you under the MIT license.\n"; +const banner_generated = banner + "//! \n//! This is generated file, see src/mono/wasm/runtime/rollup.config.js \n"; // emcc doesn't know how to load ES6 module, that's why we need the whole rollup.js const format = "iife"; const name = "__dotnet_runtime"; -export default defineConfig([ - { - treeshake: !isDebug, - input: "exports.ts", - output: [{ - file: nativeBinDir + "/src/" + outputFileName, +const iffeConfig = { + treeshake: !isDebug, + input: "exports.ts", + output: [ + { + file: nativeBinDir + "/src/cjs/runtime.cjs.iffe.js", name, banner, format, plugins, - }], - plugins: [consts({ productVersion, configuration }), typescript()] - }, - { - input: "./export-types.ts", - output: [ - // dotnet.d.ts - { - format: "es", - file: nativeBinDir + "/src/" + "dotnet.d.ts", - } - ], - plugins: [dts()], - } + }, + { + file: nativeBinDir + "/src/es6/runtime.es6.iffe.js", + name, + banner, + format, + plugins, + } + ], + plugins: [consts({ productVersion, configuration }), typescript()] +}; +const typesConfig = { + input: "./export-types.ts", + output: [ + { + format: "es", + file: nativeBinDir + "/dotnet.d.ts", + banner: banner_generated, + plugins: [writeOnChangePlugin()], + } + ], + plugins: [dts()], +}; + +if (isDebug) { + // export types also into the source code and commit to git + // so that we could notice that the API changed and review it + typesConfig.output.push({ + format: "es", + file: "./dotnet.d.ts", + banner: banner_generated, + plugins: [writeOnChangePlugin()], + }); +} + +export default defineConfig([ + iffeConfig, + typesConfig ]); // this would create .sha256 file next to the output file, so that we do not touch datetime of the file if it's same -> faster incremental build. @@ -80,7 +105,8 @@ function writeOnChangePlugin() { async function writeWhenChanged(options, bundle) { try { - const asset = bundle[outputFileName]; + const name = Object.keys(bundle)[0]; + const asset = bundle[name]; const code = asset.code; const hashFileName = options.file + ".sha256"; const oldHashExists = await checkFileExists(hashFileName); @@ -94,13 +120,14 @@ async function writeWhenChanged(options, bundle) { isOutputChanged = oldHash !== newHash; } if (isOutputChanged) { - if (!await checkFileExists(hashFileName)) { - await mkdir(nativeBinDir + "/src", { recursive: true }); + const dir = path.dirname(options.file); + if (!await checkFileExists(dir)) { + await mkdir(dir, { recursive: true }); } await writeFile(hashFileName, newHash); } else { // this.warn('No change in ' + options.file) - delete bundle[outputFileName]; + delete bundle[name]; } } catch (ex) { this.warn(ex.toString()); diff --git a/src/mono/wasm/runtime/roots.ts b/src/mono/wasm/runtime/roots.ts index 1eb88b3d7dc7..010732c23a89 100644 --- a/src/mono/wasm/runtime/roots.ts +++ b/src/mono/wasm/runtime/roots.ts @@ -3,7 +3,7 @@ import cwraps from "./cwraps"; import { Module } from "./imports"; -import { VoidPtr, ManagedPointer, NativePointer } from "./types"; +import { VoidPtr, ManagedPointer, NativePointer } from "./types/emscripten"; const maxScratchRoots = 8192; let _scratch_root_buffer: WasmRootBuffer | null = null; diff --git a/src/mono/wasm/runtime/startup.ts b/src/mono/wasm/runtime/startup.ts index e08370090f54..6ab3aaa943df 100644 --- a/src/mono/wasm/runtime/startup.ts +++ b/src/mono/wasm/runtime/startup.ts @@ -1,7 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -import { AllAssetEntryTypes, AssetEntry, CharPtr, CharPtrNull, EmscriptenModuleMono, GlobalizationMode, MonoConfig, VoidPtr, wasm_type_symbol } from "./types"; +import { AllAssetEntryTypes, AssetEntry, CharPtrNull, DotnetModule, GlobalizationMode, MonoConfig, MonoConfigError, wasm_type_symbol } from "./types"; import { ENVIRONMENT_IS_NODE, ENVIRONMENT_IS_SHELL, INTERNAL, locateFile, Module, MONO, runtimeHelpers } from "./imports"; import cwraps from "./cwraps"; import { mono_wasm_raise_debug_event, mono_wasm_runtime_ready } from "./debug"; @@ -11,6 +11,7 @@ import { mono_wasm_init_aot_profiler, mono_wasm_init_coverage_profiler } from ". import { mono_wasm_load_bytes_into_heap } from "./buffers"; import { bind_runtime_method, get_method, _create_primitive_converters } from "./method-binding"; import { find_corlib_class } from "./class-loader"; +import { VoidPtr, CharPtr } from "./types/emscripten"; export let runtime_is_initialized_resolve: Function; export let runtime_is_initialized_reject: Function; @@ -21,40 +22,44 @@ export const mono_wasm_runtime_is_initialized = new Promise((resolve, reject) => export async function mono_wasm_pre_init(): Promise { - const moduleExt = Module as EmscriptenModuleMono; - if (moduleExt.configSrc) { + const moduleExt = Module as DotnetModule; + if (!moduleExt.configSrc) { + return; + } + + try { + // sets MONO.config implicitly + await mono_wasm_load_config(moduleExt.configSrc); + } + catch (err: any) { + runtime_is_initialized_reject(err); + throw err; + } + + if (moduleExt.onConfigLoaded) { try { - // sets MONO.config implicitly - await mono_wasm_load_config(moduleExt.configSrc); + moduleExt.onConfigLoaded(); } catch (err: any) { + Module.printErr("MONO_WASM: onConfigLoaded () failed: " + err); + Module.printErr("MONO_WASM: Stacktrace: \n"); + Module.printErr(err.stack); runtime_is_initialized_reject(err); throw err; } - - if (moduleExt.onConfigLoaded) { - try { - moduleExt.onConfigLoaded(); - } - catch (err: any) { - Module.printErr("MONO_WASM: onConfigLoaded () failed: " + err); - Module.printErr("MONO_WASM: Stacktrace: \n"); - Module.printErr(err.stack); - runtime_is_initialized_reject(err); - throw err; - } - } } + } -export function mono_wasm_on_runtime_initialized(): void { - const moduleExt = Module as EmscriptenModuleMono; - if (!moduleExt.config || moduleExt.config.isError) { +export async function mono_wasm_on_runtime_initialized(): Promise { + if (!Module.config || Module.config.isError) { return; } - mono_load_runtime_and_bcl_args(moduleExt.config); + await mono_load_runtime_and_bcl_args(Module.config); + finalize_startup(Module.config); } + // Set environment variable NAME to VALUE // Should be called before mono_load_runtime_and_bcl () in most cases export function mono_wasm_setenv(name: string, value: string): void { @@ -71,44 +76,6 @@ export function mono_wasm_set_runtime_options(options: string[]): void { cwraps.mono_wasm_parse_runtime_options(options.length, argv); } -async function _fetch_asset(url: string): Promise { - try { - if (typeof (fetch) === "function") { - return fetch(url, { credentials: "same-origin" }); - } - else if (ENVIRONMENT_IS_NODE) { - //const fs = (globalThis).require("fs"); - // eslint-disable-next-line @typescript-eslint/no-var-requires - const fs = require("fs"); - const arrayBuffer = await fs.promises.readFile(url); - return { - ok: true, - url, - arrayBuffer: () => arrayBuffer, - json: () => JSON.parse(arrayBuffer) - }; - } - else if (typeof (read) === "function") { - const arrayBuffer = new Uint8Array(read(url, "binary")); - return { - ok: true, - url, - arrayBuffer: () => arrayBuffer, - json: () => JSON.parse(Module.UTF8ArrayToString(arrayBuffer, 0, arrayBuffer.length)) - }; - } - } - catch (e: any) { - return { - ok: false, - url, - arrayBuffer: () => { throw e; }, - json: () => { throw e; } - }; - } - throw new Error("No fetch implementation available"); -} - function _handle_fetched_asset(ctx: MonoInitContext, asset: AssetEntry, url: string, blob: ArrayBuffer) { const bytes = new Uint8Array(blob); if (ctx.tracing) @@ -182,89 +149,93 @@ function _handle_fetched_asset(ctx: MonoInitContext, asset: AssetEntry, url: str } } -function _apply_configuration_from_args(args: MonoConfig) { - for (const k in (args.environment_variables || {})) - mono_wasm_setenv(k, args.environment_variables![k]); +function _apply_configuration_from_args(config: MonoConfig) { + for (const k in (config.environment_variables || {})) + mono_wasm_setenv(k, config.environment_variables![k]); - if (args.runtime_options) - mono_wasm_set_runtime_options(args.runtime_options); + if (config.runtime_options) + mono_wasm_set_runtime_options(config.runtime_options); - if (args.aot_profiler_options) - mono_wasm_init_aot_profiler(args.aot_profiler_options); + if (config.aot_profiler_options) + mono_wasm_init_aot_profiler(config.aot_profiler_options); - if (args.coverage_profiler_options) - mono_wasm_init_coverage_profiler(args.coverage_profiler_options); + if (config.coverage_profiler_options) + mono_wasm_init_coverage_profiler(config.coverage_profiler_options); } -function _finalize_startup(args: MonoConfig, ctx: MonoInitContext) { - const moduleExt = Module as EmscriptenModuleMono; +function finalize_startup(config: MonoConfig | MonoConfigError | undefined): void { + try { + if (!config || config.isError) { + return; + } + if (config.diagnostic_tracing) { + console.debug("MONO_WASM: Initializing mono runtime"); + } - ctx.loaded_files.forEach(value => MONO.loaded_files.push(value.url)); - if (ctx.tracing) { - console.log("MONO_WASM: loaded_assets: " + JSON.stringify(ctx.loaded_assets)); - console.log("MONO_WASM: loaded_files: " + JSON.stringify(ctx.loaded_files)); - } + const moduleExt = Module as DotnetModule; - console.debug("MONO_WASM: Initializing mono runtime"); - - mono_wasm_globalization_init(args.globalization_mode!); - - if (ENVIRONMENT_IS_SHELL || ENVIRONMENT_IS_NODE) { try { - cwraps.mono_wasm_load_runtime("unused", args.debug_level || 0); + _apply_configuration_from_args(config); + + mono_wasm_globalization_init(config.globalization_mode!, config.diagnostic_tracing!); + cwraps.mono_wasm_load_runtime("unused", config.debug_level || 0); } catch (err: any) { Module.printErr("MONO_WASM: mono_wasm_load_runtime () failed: " + err); Module.printErr("MONO_WASM: Stacktrace: \n"); Module.printErr(err.stack); runtime_is_initialized_reject(err); - const wasm_exit = cwraps.mono_wasm_exit; - wasm_exit(1); + if (ENVIRONMENT_IS_SHELL || ENVIRONMENT_IS_NODE) { + const wasm_exit = cwraps.mono_wasm_exit; + wasm_exit(1); + } } - } else { - cwraps.mono_wasm_load_runtime("unused", args.debug_level || 0); - } - bindings_lazy_init(); + bindings_lazy_init(); - let tz; - try { - tz = Intl.DateTimeFormat().resolvedOptions().timeZone; - } catch { - //swallow - } - mono_wasm_setenv("TZ", tz || "UTC"); - mono_wasm_runtime_ready(); - - //legacy config loading - const argsAny: any = args; - if (argsAny.loaded_cb) { + let tz; try { - argsAny.loaded_cb(); + tz = Intl.DateTimeFormat().resolvedOptions().timeZone; + } catch { + //swallow } - catch (err: any) { - Module.printErr("MONO_WASM: loaded_cb () failed: " + err); - Module.printErr("MONO_WASM: Stacktrace: \n"); - Module.printErr(err.stack); - runtime_is_initialized_reject(err); - throw err; - } - } + mono_wasm_setenv("TZ", tz || "UTC"); + mono_wasm_runtime_ready(); - if (moduleExt.onDotNetReady) { - try { - moduleExt.onDotNetReady(); + //legacy config loading + const argsAny: any = config; + if (argsAny.loaded_cb) { + try { + argsAny.loaded_cb(); + } + catch (err: any) { + Module.printErr("MONO_WASM: loaded_cb () failed: " + err); + Module.printErr("MONO_WASM: Stacktrace: \n"); + Module.printErr(err.stack); + runtime_is_initialized_reject(err); + throw err; + } } - catch (err: any) { - Module.printErr("MONO_WASM: onDotNetReady () failed: " + err); - Module.printErr("MONO_WASM: Stacktrace: \n"); - Module.printErr(err.stack); - runtime_is_initialized_reject(err); - throw err; - } - } - runtime_is_initialized_resolve(); + if (moduleExt.onDotnetReady) { + try { + moduleExt.onDotnetReady(); + } + catch (err: any) { + Module.printErr("MONO_WASM: onDotnetReady () failed: " + err); + Module.printErr("MONO_WASM: Stacktrace: \n"); + Module.printErr(err.stack); + runtime_is_initialized_reject(err); + throw err; + } + } + + runtime_is_initialized_resolve(); + } catch (err: any) { + console.error("MONO_WASM: Error in finalize_startup:", err); + runtime_is_initialized_reject(err); + throw err; + } } export function bindings_lazy_init(): void { @@ -330,14 +301,20 @@ export function bindings_lazy_init(): void { } // Initializes the runtime and loads assemblies, debug information, and other files. -export async function mono_load_runtime_and_bcl_args(args: MonoConfig): Promise { - try { - if (args.enable_debugging) - args.debug_level = args.enable_debugging; +export async function mono_load_runtime_and_bcl_args(config: MonoConfig | MonoConfigError | undefined): Promise { + if (!config || config.isError) { + return; + } + try { + if (config.enable_debugging) + config.debug_level = config.enable_debugging; + + + config.diagnostic_tracing = config.diagnostic_tracing || false; const ctx: MonoInitContext = { - tracing: args.diagnostic_tracing || false, - pending_count: args.assets.length, + tracing: config.diagnostic_tracing, + pending_count: config.assets.length, loaded_assets: Object.create(null), // dlls and pdbs, used by blazor and the debugger loaded_files: [], @@ -345,14 +322,15 @@ export async function mono_load_runtime_and_bcl_args(args: MonoConfig): Promise< createDataFile: Module.FS_createDataFile }; - _apply_configuration_from_args(args); + // fetch_file_cb is legacy do we really want to support it ? + if (!Module.imports!.fetch && typeof ((config).fetch_file_cb) === "function") { + runtimeHelpers.fetch = (config).fetch_file_cb; + } - const local_fetch = typeof (args.fetch_file_cb) === "function" ? args.fetch_file_cb : _fetch_asset; + const load_asset = async (config: MonoConfig, asset: AllAssetEntryTypes): Promise => { + // TODO Module.addRunDependency(asset.name); - const load_asset = async (asset: AllAssetEntryTypes): Promise => { - //TODO we could do module.addRunDependency(asset.name) and delay emscripten run() after all assets are loaded - - const sourcesList = asset.load_remote ? args.remote_sources! : [""]; + const sourcesList = asset.load_remote ? config.remote_sources! : [""]; let error = undefined; for (let sourcePrefix of sourcesList) { // HACK: Special-case because MSBuild doesn't allow "" as an attribute @@ -362,10 +340,10 @@ export async function mono_load_runtime_and_bcl_args(args: MonoConfig): Promise< let attemptUrl; if (sourcePrefix.trim() === "") { if (asset.behavior === "assembly") - attemptUrl = locateFile(args.assembly_root + "/" + asset.name); + attemptUrl = locateFile(config.assembly_root + "/" + asset.name); else if (asset.behavior === "resource") { const path = asset.culture !== "" ? `${asset.culture}/${asset.name}` : asset.name; - attemptUrl = locateFile(args.assembly_root + "/" + path); + attemptUrl = locateFile(config.assembly_root + "/" + path); } else attemptUrl = asset.name; @@ -380,7 +358,7 @@ export async function mono_load_runtime_and_bcl_args(args: MonoConfig): Promise< console.log(`MONO_WASM: Attempting to fetch '${attemptUrl}' for ${asset.name}`); } try { - const response = await local_fetch(attemptUrl); + const response = await runtimeHelpers.fetch(attemptUrl); if (!response.ok) { error = new Error(`MONO_WASM: Fetch '${attemptUrl}' for ${asset.name} failed ${response.status} ${response.statusText}`); continue;// next source @@ -397,25 +375,29 @@ export async function mono_load_runtime_and_bcl_args(args: MonoConfig): Promise< } if (!error) { - //TODO Module.removeRunDependency(configFilePath); break; // this source worked, stop searching } } if (error) { - const isOkToFail = asset.is_optional || (asset.name.match(/\.pdb$/) && args.ignore_pdb_load_errors); + const isOkToFail = asset.is_optional || (asset.name.match(/\.pdb$/) && config.ignore_pdb_load_errors); if (!isOkToFail) throw error; } + // TODO Module.removeRunDependency(asset.name); }; const fetch_promises: Promise[] = []; // start fetching all assets in parallel - for (const asset of args.assets) { - fetch_promises.push(load_asset(asset)); + for (const asset of config.assets) { + fetch_promises.push(load_asset(config, asset)); } await Promise.all(fetch_promises); - _finalize_startup(args, ctx); + ctx.loaded_files.forEach(value => MONO.loaded_files.push(value.url)); + if (ctx.tracing) { + console.log("MONO_WASM: loaded_assets: " + JSON.stringify(ctx.loaded_assets)); + console.log("MONO_WASM: loaded_files: " + JSON.stringify(ctx.loaded_files)); + } } catch (err: any) { console.error("MONO_WASM: Error in mono_load_runtime_and_bcl_args:", err); runtime_is_initialized_reject(err); @@ -489,7 +471,7 @@ export async function mono_wasm_load_config(configFilePath: string): Promise Promise; } export const wasm_type_symbol = Symbol.for("wasm type"); @@ -179,19 +154,36 @@ export type CoverageProfilerOptions = { } // how we extended emscripten Module -export type EmscriptenModuleMono = EmscriptenModule & EmscriptenModuleConfig; +export type DotnetModule = EmscriptenModule & DotnetModuleConfig; -export type EmscriptenModuleConfig = { - disableDotNet6Compatibility?: boolean, +export type DotnetModuleConfig = { + disableDotnet6Compatibility?: boolean, - // backward compatibility config?: MonoConfig | MonoConfigError, configSrc?: string, + scriptDirectory?: string, onConfigLoaded?: () => void; - onDotNetReady?: () => void; + onDotnetReady?: () => void; - /** - * @deprecated DEPRECATED! backward compatibility https://github.com/search?q=mono_bind_static_method&type=Code - */ - mono_bind_static_method: (fqn: string, signature: string) => Function, -} + imports?: DotnetModuleConfigImports; +} & EmscriptenModule + +export type DotnetModuleConfigImports = { + require?: (name: string) => any; + fetch?: (url: string) => Promise; + fs?: { + promises?: { + readFile?: (path: string) => Promise, + } + readFileSync?: (path: string, options: any | undefined) => string, + }; + crypto?: { + randomBytes?: (size: number) => Buffer + }; + ws?: WebSocket & { Server: any }; + path?: { + normalize?: (path: string) => string, + dirname?: (path: string) => string, + }; + url?: any; +} \ No newline at end of file diff --git a/src/mono/wasm/runtime/types/emscripten.d.ts b/src/mono/wasm/runtime/types/emscripten.ts similarity index 70% rename from src/mono/wasm/runtime/types/emscripten.d.ts rename to src/mono/wasm/runtime/types/emscripten.ts index 51dbcbe364cf..88fe9548bced 100644 --- a/src/mono/wasm/runtime/types/emscripten.d.ts +++ b/src/mono/wasm/runtime/types/emscripten.ts @@ -1,28 +1,28 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -declare interface ManagedPointer { +export declare interface ManagedPointer { __brandManagedPointer: "ManagedPointer" } -declare interface NativePointer { +export declare interface NativePointer { __brandNativePointer: "NativePointer" } -declare interface VoidPtr extends NativePointer { +export declare interface VoidPtr extends NativePointer { __brand: "VoidPtr" } -declare interface CharPtr extends NativePointer { +export declare interface CharPtr extends NativePointer { __brand: "CharPtr" } -declare interface Int32Ptr extends NativePointer { +export declare interface Int32Ptr extends NativePointer { __brand: "Int32Ptr" } -declare interface CharPtrPtr extends NativePointer { +export declare interface CharPtrPtr extends NativePointer { __brand: "CharPtrPtr" } -declare interface EmscriptenModule { +export declare interface EmscriptenModule { HEAP8: Int8Array, HEAP16: Int16Array; HEAP32: Int32Array; @@ -52,8 +52,12 @@ declare interface EmscriptenModule { removeRunDependency(id: string): void; addRunDependency(id: string): void; - preInit?: (() => Promise)[]; - onRuntimeInitialized?: () => void; + ready: Promise; + preInit?: (() => any)[]; + preRun?: (() => any)[]; + postRun?: (() => any)[]; + onRuntimeInitialized?: () => any; + instantiateWasm: (imports: any, successCallback: Function) => any; } -declare type TypedArray = Int8Array | Uint8Array | Uint8ClampedArray | Int16Array | Uint16Array | Int32Array | Uint32Array | Float32Array | Float64Array; \ No newline at end of file +export declare type TypedArray = Int8Array | Uint8Array | Uint8ClampedArray | Int16Array | Uint16Array | Int32Array | Uint32Array | Float32Array | Float64Array; \ No newline at end of file diff --git a/src/mono/wasm/runtime/web-socket.ts b/src/mono/wasm/runtime/web-socket.ts index 319af2f46504..3a6753b4f7fc 100644 --- a/src/mono/wasm/runtime/web-socket.ts +++ b/src/mono/wasm/runtime/web-socket.ts @@ -10,8 +10,9 @@ import { mono_wasm_get_jsobj_from_js_handle, mono_wasm_get_js_handle } from "./g import { _wrap_js_thenable_as_task } from "./js-to-cs"; import { wrap_error } from "./method-calls"; import { conv_string } from "./strings"; -import { Int32Ptr, JSHandle, MonoArray, MonoObject, MonoObjectNull, MonoString } from "./types"; +import { JSHandle, MonoArray, MonoObject, MonoObjectNull, MonoString } from "./types"; import { Module } from "./imports"; +import { Int32Ptr } from "./types/emscripten"; const wasm_ws_pending_send_buffer = Symbol.for("wasm ws_pending_send_buffer"); const wasm_ws_pending_send_buffer_offset = Symbol.for("wasm ws_pending_send_buffer_offset"); diff --git a/src/mono/wasm/test-main.js b/src/mono/wasm/test-main.js index 8ef7f699b10e..84b26ec78bac 100644 --- a/src/mono/wasm/test-main.js +++ b/src/mono/wasm/test-main.js @@ -67,33 +67,34 @@ for (let m of methods) { } } -function proxyJson(func) { - for (let m of ["log", ...methods]) - console[m] = proxyMethod(`console.${m}`, func, true); -} - let consoleWebSocket; if (is_browser) { const consoleUrl = `${window.location.origin}/console`.replace('http://', 'ws://'); consoleWebSocket = new WebSocket(consoleUrl); - // redirect output so that when emscripten starts it's already redirected - proxyJson(function (msg) { - if (consoleWebSocket.readyState === WebSocket.OPEN) { - consoleWebSocket.send(msg); - } - else { - originalConsole.log(msg); - } - }); - consoleWebSocket.onopen = function (event) { originalConsole.log("browser: Console websocket connected."); }; consoleWebSocket.onerror = function (event) { originalConsole.error(`websocket error: ${event}`); }; + consoleWebSocket.onclose = function (event) { + originalConsole.error(`websocket closed: ${event}`); + }; + + const send = (msg) => { + if (consoleWebSocket.readyState === WebSocket.OPEN) { + consoleWebSocket.send(msg); + } + else { + originalConsole.log(msg); + } + } + + // redirect output early, so that when emscripten starts it's already redirected + for (let m of ["log", ...methods]) + console[m] = proxyMethod(`console.${m}`, send, true); } if (typeof globalThis.crypto === 'undefined') { @@ -121,48 +122,56 @@ if (typeof globalThis.performance === 'undefined') { } } } -var Module = { - config: null, - configSrc: "./mono-config.json", - onConfigLoaded: () => { - if (!Module.config) { - const err = new Error("Could not find ./mono-config.json. Cancelling run"); - set_exit_code(1,); - throw err; - } - // Have to set env vars here to enable setting MONO_LOG_LEVEL etc. - for (let variable in processedArguments.setenv) { - Module.config.environment_variables[variable] = processedArguments.setenv[variable]; - } +loadDotnet("./dotnet.js").then((createDotnetRuntime) => { + return createDotnetRuntime(({ MONO, INTERNAL, BINDING, Module }) => ({ + disableDotnet6Compatibility: true, + config: null, + configSrc: "./mono-config.json", + onConfigLoaded: () => { + if (!Module.config) { + const err = new Error("Could not find ./mono-config.json. Cancelling run"); + set_exit_code(1,); + throw err; + } + // Have to set env vars here to enable setting MONO_LOG_LEVEL etc. + for (let variable in processedArguments.setenv) { + Module.config.environment_variables[variable] = processedArguments.setenv[variable]; + } - if (!processedArguments.enable_gc) { - INTERNAL.mono_wasm_enable_on_demand_gc(0); - } - }, - onDotNetReady: () => { - let wds = Module.FS.stat(processedArguments.working_dir); - if (wds === undefined || !Module.FS.isDir(wds.mode)) { - set_exit_code(1, `Could not find working directory ${processedArguments.working_dir}`); - return; - } + if (!processedArguments.enable_gc) { + INTERNAL.mono_wasm_enable_on_demand_gc(0); + } + }, + onDotnetReady: () => { + let wds = Module.FS.stat(processedArguments.working_dir); + if (wds === undefined || !Module.FS.isDir(wds.mode)) { + set_exit_code(1, `Could not find working directory ${processedArguments.working_dir}`); + return; + } - Module.FS.chdir(processedArguments.working_dir); - - App.init(); - }, - onAbort: (error) => { - console.log("ABORT: " + error); - const err = new Error(); - console.log("Stacktrace: \n"); - console.error(err.stack); - set_exit_code(1, error); - }, -}; + Module.FS.chdir(processedArguments.working_dir); + App.init({ MONO, INTERNAL, BINDING, Module }); + }, + onAbort: (error) => { + console.log("ABORT: " + error); + const err = new Error(); + console.log("Stacktrace: \n"); + console.error(err.stack); + set_exit_code(1, error); + }, + })) +}).catch(function (err) { + console.error(err); + set_exit_code(1, "failed to load the dotnet.js file"); + throw err; +}); const App = { - init: async function () { + init: async function ({ MONO, INTERNAL, BINDING, Module }) { console.info("Initializing....."); + Object.assign(App, { MONO, INTERNAL, BINDING, Module }); + for (let i = 0; i < processedArguments.profilers.length; ++i) { const init = Module.cwrap('mono_wasm_load_profiler_' + processedArguments.profilers[i], 'void', ['string']); init(""); @@ -205,7 +214,7 @@ const App = { const main_assembly_name = processedArguments.applicationArgs[1]; const app_args = processedArguments.applicationArgs.slice(2); - INTERNAL.mono_wasm_set_main_args(processedArguments.applicationArgs[1], app_args); + INTERNAL.mono_wasm_set_main_args(main_assembly_name, app_args); // Automatic signature isn't working correctly const result = await BINDING.call_assembly_entry_point(main_assembly_name, [app_args], "m"); @@ -228,7 +237,7 @@ const App = { const fqn = "[System.Private.Runtime.InteropServices.JavaScript.Tests]System.Runtime.InteropServices.JavaScript.Tests.HelperMarshal:" + method_name; try { - return INTERNAL.call_static_method(fqn, args || [], signature); + return App.INTERNAL.call_static_method(fqn, args || [], signature); } catch (exc) { console.error("exception thrown in", fqn); throw exc; @@ -244,12 +253,12 @@ function set_exit_code(exit_code, reason) { console.error(reason.stack); } } - if (is_browser) { - const stack = (new Error()).stack.replace(/\n/g, "").replace(/[ ]*at/g, " at").replace(/https?:\/\/[0-9.:]*/g, "").replace("Error", ""); - const messsage = `Exit called with ${exit_code} when isXUnitDoneCheck=${isXUnitDoneCheck} isXmlDoneCheck=${isXmlDoneCheck} WS.bufferedAmount=${consoleWebSocket.bufferedAmount} ${stack}.`; - // Notify the selenium script - Module.exit_code = exit_code; + if (is_browser) { + if (App.Module) { + // Notify the selenium script + App.Module.exit_code = exit_code; + } //Tell xharness WasmBrowserTestRunner what was the exit code const tests_done_elem = document.createElement("label"); @@ -257,8 +266,6 @@ function set_exit_code(exit_code, reason) { tests_done_elem.innerHTML = exit_code.toString(); document.body.appendChild(tests_done_elem); - console.log('WS: ' + messsage); - originalConsole.log('CDP: ' + messsage); const stop_when_ws_buffer_empty = () => { if (consoleWebSocket.bufferedAmount == 0) { // tell xharness WasmTestMessagesProcessor we are done. @@ -271,8 +278,8 @@ function set_exit_code(exit_code, reason) { }; stop_when_ws_buffer_empty(); - } else if (INTERNAL) { - INTERNAL.mono_wasm_exit(exit_code); + } else if (App && App.INTERNAL) { + App.INTERNAL.mono_wasm_exit(exit_code); } } @@ -354,7 +361,7 @@ async function loadDotnet(file) { if (typeof WScript !== "undefined") { // Chakra loadScript = function (file) { WScript.LoadScriptFile(file); - return globalThis.Module; + return globalThis.createDotnetRuntime; }; } else if (is_node) { // NodeJS loadScript = async function (file) { @@ -368,8 +375,8 @@ async function loadDotnet(file) { let timeout = 100; // bysy spin waiting for script to load into global namespace while (timeout > 0) { - if (globalThis.Module) { - return globalThis.Module; + if (globalThis.createDotnetRuntime) { + return globalThis.createDotnetRuntime; } // delay 10ms await new Promise(resolve => setTimeout(resolve, 10)); @@ -381,7 +388,7 @@ async function loadDotnet(file) { else if (typeof globalThis.load !== 'undefined') { loadScript = async function (file) { globalThis.load(file) - return globalThis.Module; + return globalThis.createDotnetRuntime; } } else { @@ -390,9 +397,3 @@ async function loadDotnet(file) { return loadScript(file); } - -loadDotnet("./dotnet.js").catch(function (err) { - console.error(err); - set_exit_code(1, "failed to load the dotnet.js file"); - throw err; -}); \ No newline at end of file diff --git a/src/mono/wasm/wasm.proj b/src/mono/wasm/wasm.proj index b31983a86660..0a8e59dd4393 100644 --- a/src/mono/wasm/wasm.proj +++ b/src/mono/wasm/wasm.proj @@ -66,13 +66,14 @@ <_EmccCommonFlags Include="-s ALLOW_MEMORY_GROWTH=1" /> <_EmccCommonFlags Include="-s NO_EXIT_RUNTIME=1" /> <_EmccCommonFlags Include="-s FORCE_FILESYSTEM=1" /> - <_EmccCommonFlags Include="-s EXPORTED_RUNTIME_METHODS="['DOTNET','MONO','BINDING','INTERNAL','FS','print','ccall','cwrap','setValue','getValue','UTF8ToString','UTF8ArrayToString','FS_createPath','FS_createDataFile','removeRunDependency','addRunDependency']"" /> + <_EmccCommonFlags Include="-s EXPORTED_RUNTIME_METHODS="['FS','print','ccall','cwrap','setValue','getValue','UTF8ToString','UTF8ArrayToString','FS_createPath','FS_createDataFile','removeRunDependency','addRunDependency']"" /> <_EmccCommonFlags Include="-s EXPORTED_FUNCTIONS="['_free','_malloc']"" /> <_EmccCommonFlags Include="--source-map-base http://example.com" /> <_EmccCommonFlags Include="-s STRICT_JS=1" /> - <_EmccCommonFlags Include="-s MODULARIZE=1" Condition="'$(WasmEnableES6)' != 'false'" /> - <_EmccCommonFlags Include="-s EXPORT_ES6=1" Condition="'$(WasmEnableES6)' != 'false'" /> + <_EmccCommonFlags Include="-s EXPORT_NAME="'createDotnetRuntime'"" /> + <_EmccCommonFlags Include="-s MODULARIZE=1"/> + <_EmccCommonFlags Include="-s EXPORT_ES6=1" Condition="'$(WasmEnableES6)' == 'true'" /> @@ -152,7 +153,7 @@ $(CMakeConfigurationEmccFlags) -O2 -DEMSDK_PATH="$(EMSDK_PATH.TrimEnd('\/'))" - emcmake cmake $(MSBuildThisFileDirectory)runtime -DCMAKE_BUILD_TYPE=$(Configuration) -DCONFIGURATION_EMCC_FLAGS="$(CMakeConfigurationEmccFlags)" -DCONFIGURATION_LINK_FLAGS="$(CMakeConfigurationLinkFlags)" -DMONO_INCLUDES="$(MonoArtifactsPath)include/mono-2.0" -DMONO_OBJ_INCLUDES="$(MonoObjDir.TrimEnd('\/'))" -DICU_LIB_DIR="$(ICULibDir.TrimEnd('\/'))" -DMONO_ARTIFACTS_DIR="$(MonoArtifactsPath.TrimEnd('\/'))" -DNATIVE_BIN_DIR="$(NativeBinDir.TrimEnd('\/'))" -DSYSTEM_NATIVE_DIR="$(RepoRoot)src/native/libs/System.Native" -DSOURCE_DIR="$(MSBuildThisFileDirectory.TrimEnd('\/'))/runtime"$(CMakeConfigurationEmsdkPath) + emcmake cmake $(MSBuildThisFileDirectory)runtime -DCMAKE_BUILD_TYPE=$(Configuration) -DCONFIGURATION_EMCC_FLAGS="$(CMakeConfigurationEmccFlags)" -DCONFIGURATION_LINK_FLAGS="$(CMakeConfigurationLinkFlags)" -DMONO_INCLUDES="$(MonoArtifactsPath)include/mono-2.0" -DMONO_OBJ_INCLUDES="$(MonoObjDir.TrimEnd('\/'))" -DICU_LIB_DIR="$(ICULibDir.TrimEnd('\/'))" -DMONO_ARTIFACTS_DIR="$(MonoArtifactsPath.TrimEnd('\/'))" -DNATIVE_BIN_DIR="$(NativeBinDir.TrimEnd('\/'))" $(CMakeConfigurationEmsdkPath) call "$(RepositoryEngineeringDir)native\init-vs-env.cmd" && call "$([MSBuild]::NormalizePath('$(EMSDK_PATH)', 'emsdk_env.bat'))" && $(CMakeBuildRuntimeConfigureCmd) bash -c 'source $(EMSDK_PATH)/emsdk_env.sh 2>&1 && $(CMakeBuildRuntimeConfigureCmd)' @@ -180,16 +181,30 @@ + + + + - @@ -202,11 +217,14 @@ $(NativeBinDir)src\*.js; $(_EmccDefaultsRspPath); $(NativeBinDir)src\emcc-props.json" /> + + + + + + diff --git a/src/native/libs/System.Native/pal_random.js b/src/native/libs/System.Native/pal_random.lib.js similarity index 100% rename from src/native/libs/System.Native/pal_random.js rename to src/native/libs/System.Native/pal_random.lib.js diff --git a/src/tests/FunctionalTests/WebAssembly/Browser/HotReload/main.js b/src/tests/FunctionalTests/WebAssembly/Browser/HotReload/main.js index 861ddd263cd8..5c56c3d8092f 100644 --- a/src/tests/FunctionalTests/WebAssembly/Browser/HotReload/main.js +++ b/src/tests/FunctionalTests/WebAssembly/Browser/HotReload/main.js @@ -8,7 +8,7 @@ var Module = { onConfigLoaded: () => { MONO.config.environment_variables["DOTNET_MODIFIABLE_ASSEMBLIES"] = "debug"; }, - onDotNetReady: () => { + onDotnetReady: () => { try { App.init(); } catch (error) { diff --git a/src/tests/FunctionalTests/WebAssembly/Browser/RuntimeConfig/main.js b/src/tests/FunctionalTests/WebAssembly/Browser/RuntimeConfig/main.js index 88db9c67c1dd..917fb89ba5e9 100644 --- a/src/tests/FunctionalTests/WebAssembly/Browser/RuntimeConfig/main.js +++ b/src/tests/FunctionalTests/WebAssembly/Browser/RuntimeConfig/main.js @@ -5,7 +5,7 @@ var Module = { configSrc: "./mono-config.json", - onDotNetReady: () => { + onDotnetReady: () => { try { App.init(); } catch (error) {