From e8c094d6482ed5c97a4428a5f260545f7a2bb07b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Fi=C5=A1era?= Date: Mon, 31 Oct 2022 14:28:32 +0100 Subject: [PATCH 01/33] Hacking.. --- src/mono/sample/wasm/browser/Program.cs | 5 +- .../wasm/browser/Wasm.Browser.Sample.csproj | 2 + src/mono/sample/wasm/browser/main.js | 56 ++++++++++++------- src/mono/wasm/build/WasmApp.targets | 2 +- src/mono/wasm/runtime/assets.ts | 5 ++ src/mono/wasm/runtime/startup.ts | 4 +- src/mono/wasm/runtime/types.ts | 1 + 7 files changed, 48 insertions(+), 27 deletions(-) diff --git a/src/mono/sample/wasm/browser/Program.cs b/src/mono/sample/wasm/browser/Program.cs index ce542da6201921..aba9c37c475a52 100644 --- a/src/mono/sample/wasm/browser/Program.cs +++ b/src/mono/sample/wasm/browser/Program.cs @@ -11,11 +11,8 @@ public partial class Test { public static int Main(string[] args) { - DisplayMeaning(42); + Console.WriteLine("MF .NET"); return 0; } - - [JSImport("Sample.Test.displayMeaning", "main.js")] - internal static partial void DisplayMeaning(int meaning); } } diff --git a/src/mono/sample/wasm/browser/Wasm.Browser.Sample.csproj b/src/mono/sample/wasm/browser/Wasm.Browser.Sample.csproj index 76ec8950104ab5..3d64e4e2c6a72c 100644 --- a/src/mono/sample/wasm/browser/Wasm.Browser.Sample.csproj +++ b/src/mono/sample/wasm/browser/Wasm.Browser.Sample.csproj @@ -1,6 +1,8 @@ + true + false <_SampleProject>Wasm.Browser.Sample.csproj diff --git a/src/mono/sample/wasm/browser/main.js b/src/mono/sample/wasm/browser/main.js index 27783085b924ce..444ac3d20c9ae6 100644 --- a/src/mono/sample/wasm/browser/main.js +++ b/src/mono/sample/wasm/browser/main.js @@ -1,27 +1,43 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -import { dotnet, exit } from './dotnet.js' +import { dotnet as dotnet1 } from './dotnet.js?1' +import { dotnet as dotnet2 } from './dotnet.js?2' -function displayMeaning(meaning) { - document.getElementById("out").innerHTML = `${meaning}`; -} +const api1 = await dotnet1.create(); -try { - const { setModuleImports } = await dotnet - .withElementOnExit() - .create(); +globalThis.Module1 = api1.Module['asm']['memory']; - setModuleImports("main.js", { - Sample: { - Test: { - displayMeaning - } - } - }); +const api2 = await dotnet2 + .withConfig({ + mainAssemblyName: "Wasm.Browser.Sample.dll", + assets: [ + { + virtualPath: "runtimeconfig.bin", + behavior: "vfs", + name: "supportFiles/0_runtimeconfig.bin" + }, + { + loadRemote: false, + behavior: "icu", + name: "icudt.dat" + }, + { + virtualPath: "/usr/share/zoneinfo/", + behavior: "vfs", + name: "dotnet.timezones.blat" + }, + { + behavior: "dotnetwasm", + name: "dotnet.wasm" + }], + memory: api1.Module['asm']['memory'] + }) + .withModuleConfig({ + configSrc: null + }) + .create(); - await dotnet.run(); -} -catch (err) { - exit(2, err); -} \ No newline at end of file +globalThis.Module2 = api2.Module['asm']['memory']; + +await dotnet2.run(); \ No newline at end of file diff --git a/src/mono/wasm/build/WasmApp.targets b/src/mono/wasm/build/WasmApp.targets index aa9832ada35215..ca37c9ae3ac741 100644 --- a/src/mono/wasm/build/WasmApp.targets +++ b/src/mono/wasm/build/WasmApp.targets @@ -346,7 +346,7 @@ <_WasmPThreadPoolSize Condition="'$(_WasmPThreadPoolSize)' == '' and ('$(WasmEnableThreads)' == 'true' or '$(WasmEnablePerfTracing)' == 'true')">-1 - + void = module.onRuntimeInitialized ? module.onRuntimeInitialized : () => { }; // when assets don't contain DLLs it means this is Blazor or another custom startup - isCustomStartup = !module.configSrc && (!module.config || !module.config.assets || module.config.assets.findIndex(a => a.behavior === "assembly") == -1); // like blazor + isCustomStartup = false; // like blazor // execution order == [0] == // - default or user Module.instantiateWasm (will start downloading dotnet.wasm) @@ -69,7 +69,7 @@ export function configure_emscripten_startup(module: DotnetModule, exportedAPI: // execution order == [2] == module.preRun = [() => preRunAsync(userPreRun)]; // execution order == [4] == - module.onRuntimeInitialized = () => onRuntimeInitializedAsync(userOnRuntimeInitialized); + module.onRuntimeInitialized = () => { console.log("MF onInit"); onRuntimeInitializedAsync(userOnRuntimeInitialized); }; // execution order == [5] == module.postRun = [() => postRunAsync(userpostRun)]; // execution order == [6] == diff --git a/src/mono/wasm/runtime/types.ts b/src/mono/wasm/runtime/types.ts index 81739f9a0b3a90..922e3a20fb4851 100644 --- a/src/mono/wasm/runtime/types.ts +++ b/src/mono/wasm/runtime/types.ts @@ -115,6 +115,7 @@ export type MonoConfig = { * initial number of workers to add to the emscripten pthread pool */ pthreadPoolSize?: number, + memory?: WebAssembly.Memory, }; export type MonoConfigInternal = MonoConfig & { From 50439ab4f9f0177608458c817c67a5d0dbc7a43e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Fi=C5=A1era?= Date: Tue, 8 Nov 2022 10:29:33 +0100 Subject: [PATCH 02/33] Copy memory after views are created. --- src/mono/sample/wasm/browser/Program.cs | 2 +- src/mono/sample/wasm/browser/main.js | 4 ++-- src/mono/wasm/runtime/assets.ts | 4 ---- src/mono/wasm/runtime/dotnet.d.ts | 1 + src/mono/wasm/runtime/polyfills.ts | 4 ++++ src/mono/wasm/runtime/startup.ts | 5 ++++- src/mono/wasm/runtime/types.ts | 2 +- 7 files changed, 13 insertions(+), 9 deletions(-) diff --git a/src/mono/sample/wasm/browser/Program.cs b/src/mono/sample/wasm/browser/Program.cs index aba9c37c475a52..191476828d477c 100644 --- a/src/mono/sample/wasm/browser/Program.cs +++ b/src/mono/sample/wasm/browser/Program.cs @@ -11,7 +11,7 @@ public partial class Test { public static int Main(string[] args) { - Console.WriteLine("MF .NET"); + Console.WriteLine($"MF .NET, args: {String.Join(", ", args)}"); return 0; } } diff --git a/src/mono/sample/wasm/browser/main.js b/src/mono/sample/wasm/browser/main.js index 444ac3d20c9ae6..51523ea80ee6eb 100644 --- a/src/mono/sample/wasm/browser/main.js +++ b/src/mono/sample/wasm/browser/main.js @@ -31,7 +31,7 @@ const api2 = await dotnet2 behavior: "dotnetwasm", name: "dotnet.wasm" }], - memory: api1.Module['asm']['memory'] + memory: api1.Module.HEAP8 }) .withModuleConfig({ configSrc: null @@ -40,4 +40,4 @@ const api2 = await dotnet2 globalThis.Module2 = api2.Module['asm']['memory']; -await dotnet2.run(); \ No newline at end of file +await dotnet2.run(["Runtime 2"]); \ No newline at end of file diff --git a/src/mono/wasm/runtime/assets.ts b/src/mono/wasm/runtime/assets.ts index 348d65aafa22c7..812e30ccbfcfd6 100644 --- a/src/mono/wasm/runtime/assets.ts +++ b/src/mono/wasm/runtime/assets.ts @@ -447,10 +447,6 @@ export async function instantiate_wasm_asset( if (typeof WebAssembly.instantiateStreaming === "function" && contentType === "application/wasm") { if (runtimeHelpers.diagnosticTracing) console.debug("MONO_WASM: instantiate_wasm_module streaming"); - if (runtimeHelpers.config.memory) { - wasmModuleImports['js']['mem'] = runtimeHelpers.config.memory; - } - const streamingResult = await WebAssembly.instantiateStreaming(response, wasmModuleImports!); compiledInstance = streamingResult.instance; compiledModule = streamingResult.module; diff --git a/src/mono/wasm/runtime/dotnet.d.ts b/src/mono/wasm/runtime/dotnet.d.ts index c51064343d22df..b5110e2fcd3d43 100644 --- a/src/mono/wasm/runtime/dotnet.d.ts +++ b/src/mono/wasm/runtime/dotnet.d.ts @@ -128,6 +128,7 @@ type MonoConfig = { * initial number of workers to add to the emscripten pthread pool */ pthreadPoolSize?: number; + memory?: Int8Array; }; interface ResourceRequest { name: string; diff --git a/src/mono/wasm/runtime/polyfills.ts b/src/mono/wasm/runtime/polyfills.ts index 58f1bdd13eec06..c17081c5dc75ce 100644 --- a/src/mono/wasm/runtime/polyfills.ts +++ b/src/mono/wasm/runtime/polyfills.ts @@ -185,6 +185,10 @@ export function init_polyfills(replacements: EarlyReplacements): void { replacements.updateGlobalBufferAndViews = (buffer: ArrayBufferLike) => { originalUpdateGlobalBufferAndViews(buffer); afterUpdateGlobalBufferAndViews(buffer); + + if (runtimeHelpers.config.memory) { + Module.HEAP8.set(runtimeHelpers.config.memory, 0); + } }; } diff --git a/src/mono/wasm/runtime/startup.ts b/src/mono/wasm/runtime/startup.ts index 8b6c0711e92949..8f0db8230b8e94 100644 --- a/src/mono/wasm/runtime/startup.ts +++ b/src/mono/wasm/runtime/startup.ts @@ -506,7 +506,10 @@ export function mono_wasm_load_runtime(unused?: string, debugLevel?: number): vo debugLevel = 0 + debugLevel; } } - cwraps.mono_wasm_load_runtime(unused || "unused", debugLevel); + + if (!runtimeHelpers.config.memory) { + cwraps.mono_wasm_load_runtime(unused || "unused", debugLevel); + } endMeasure(mark, MeasuredBlock.loadRuntime); runtimeHelpers.waitForDebugger = config.waitForDebugger; diff --git a/src/mono/wasm/runtime/types.ts b/src/mono/wasm/runtime/types.ts index 922e3a20fb4851..dc4ae1237ed4b5 100644 --- a/src/mono/wasm/runtime/types.ts +++ b/src/mono/wasm/runtime/types.ts @@ -115,7 +115,7 @@ export type MonoConfig = { * initial number of workers to add to the emscripten pthread pool */ pthreadPoolSize?: number, - memory?: WebAssembly.Memory, + memory?: Int8Array, }; export type MonoConfigInternal = MonoConfig & { From 9fbac0d17ebdc6de74bb3882840a181e51561ffa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Fi=C5=A1era?= Date: Wed, 9 Nov 2022 10:46:38 +0100 Subject: [PATCH 03/33] Import and export memory. --- src/mono/sample/wasm/browser/main.js | 103 ++++++++++++++++----------- 1 file changed, 63 insertions(+), 40 deletions(-) diff --git a/src/mono/sample/wasm/browser/main.js b/src/mono/sample/wasm/browser/main.js index 51523ea80ee6eb..c8a97f53595110 100644 --- a/src/mono/sample/wasm/browser/main.js +++ b/src/mono/sample/wasm/browser/main.js @@ -1,43 +1,66 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -import { dotnet as dotnet1 } from './dotnet.js?1' -import { dotnet as dotnet2 } from './dotnet.js?2' - -const api1 = await dotnet1.create(); - -globalThis.Module1 = api1.Module['asm']['memory']; - -const api2 = await dotnet2 - .withConfig({ - mainAssemblyName: "Wasm.Browser.Sample.dll", - assets: [ - { - virtualPath: "runtimeconfig.bin", - behavior: "vfs", - name: "supportFiles/0_runtimeconfig.bin" - }, - { - loadRemote: false, - behavior: "icu", - name: "icudt.dat" - }, - { - virtualPath: "/usr/share/zoneinfo/", - behavior: "vfs", - name: "dotnet.timezones.blat" - }, - { - behavior: "dotnetwasm", - name: "dotnet.wasm" - }], - memory: api1.Module.HEAP8 - }) - .withModuleConfig({ - configSrc: null - }) - .create(); - -globalThis.Module2 = api2.Module['asm']['memory']; - -await dotnet2.run(["Runtime 2"]); \ No newline at end of file +function exportMemory(memory) { + const blob = new Blob([memory], { type: 'application/octet-stream' }); + + let donwloadLink = document.createElement('a') + donwloadLink.type = 'download' + donwloadLink.href = URL.createObjectURL(blob) + donwloadLink.download = 'memory.data' + donwloadLink.click() +} + +async function importMemory() { + const response = await fetch("/memory.data"); + const buffer = await response.arrayBuffer(); + return new Int8Array(buffer); +} + +async function runtime1() { + const dotnet1 = (await import("./dotnet.js?1")).dotnet; + const api1 = await dotnet1.create(); + globalThis.Module1 = api1.Module['asm']['memory']; + exportMemory(api1.Module.HEAP8); +} + +async function runtime2() { + const dotnet2 = (await import("./dotnet.js?2")).dotnet; + const memory = await importMemory(); + const api2 = await dotnet2 + .withConfig({ + mainAssemblyName: "Wasm.Browser.Sample.dll", + assets: [ + { + virtualPath: "runtimeconfig.bin", + behavior: "vfs", + name: "supportFiles/0_runtimeconfig.bin" + }, + { + loadRemote: false, + behavior: "icu", + name: "icudt.dat" + }, + { + virtualPath: "/usr/share/zoneinfo/", + behavior: "vfs", + name: "dotnet.timezones.blat" + }, + { + behavior: "dotnetwasm", + name: "dotnet.wasm" + }], + memory: memory + }) + .withModuleConfig({ + configSrc: null + }) + .create(); + + globalThis.Module2 = api2.Module['asm']['memory']; + + await dotnet2.run(["Runtime 2"]); +} + +// runtime1(); +runtime2(); \ No newline at end of file From b11901700b5725888b0de5f2a02bd1f31fd51861 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Fi=C5=A1era?= Date: Wed, 9 Nov 2022 11:08:22 +0100 Subject: [PATCH 04/33] JSImport and JSExport. --- src/mono/sample/wasm/browser/Program.cs | 8 +++++++- src/mono/sample/wasm/browser/index.html | 2 +- src/mono/sample/wasm/browser/main.js | 22 +++++++++++++++------- 3 files changed, 23 insertions(+), 9 deletions(-) diff --git a/src/mono/sample/wasm/browser/Program.cs b/src/mono/sample/wasm/browser/Program.cs index 191476828d477c..c79d30d1a40507 100644 --- a/src/mono/sample/wasm/browser/Program.cs +++ b/src/mono/sample/wasm/browser/Program.cs @@ -11,8 +11,14 @@ public partial class Test { public static int Main(string[] args) { - Console.WriteLine($"MF .NET, args: {String.Join(", ", args)}"); + Console.WriteLine($"MF .NET, args: {String.Join(", ", args)}, location: '{GetLocation()}'"); return 0; } + + [JSImport("location.href", "main.js")] + internal static partial string GetLocation(); + + [JSExport] + internal static string Greet() => "MF JSExport"; } } diff --git a/src/mono/sample/wasm/browser/index.html b/src/mono/sample/wasm/browser/index.html index b4c21ec1b92e2e..7ade7fdc495544 100644 --- a/src/mono/sample/wasm/browser/index.html +++ b/src/mono/sample/wasm/browser/index.html @@ -12,7 +12,7 @@ - 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/main.js b/src/mono/sample/wasm/browser/main.js index c8a97f53595110..786716eaf5ce50 100644 --- a/src/mono/sample/wasm/browser/main.js +++ b/src/mono/sample/wasm/browser/main.js @@ -18,16 +18,17 @@ async function importMemory() { } async function runtime1() { + console.log("Runtime 1"); const dotnet1 = (await import("./dotnet.js?1")).dotnet; const api1 = await dotnet1.create(); - globalThis.Module1 = api1.Module['asm']['memory']; exportMemory(api1.Module.HEAP8); } async function runtime2() { - const dotnet2 = (await import("./dotnet.js?2")).dotnet; + console.log("Runtime 2"); + const dotnet = (await import("./dotnet.js?2")).dotnet; const memory = await importMemory(); - const api2 = await dotnet2 + const { setModuleImports, getAssemblyExports, getConfig } = await dotnet .withConfig({ mainAssemblyName: "Wasm.Browser.Sample.dll", assets: [ @@ -57,10 +58,17 @@ async function runtime2() { }) .create(); - globalThis.Module2 = api2.Module['asm']['memory']; + setModuleImports("main.js", { location: { href: () => window.location.href } }); - await dotnet2.run(["Runtime 2"]); + await dotnet.run(["Runtime 2"]); + + const exports = await getAssemblyExports(getConfig().mainAssemblyName); + console.log(exports.Sample.Test.Greet()); } -// runtime1(); -runtime2(); \ No newline at end of file +const runtimeParam = new URLSearchParams(location.search).get("runtime"); +if (runtimeParam === "1") { + runtime1(); +} else { + runtime2(); +} \ No newline at end of file From bfe721e3569aac4b5d44aef6556bf170f1e6f6ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Fi=C5=A1era?= Date: Wed, 9 Nov 2022 11:13:53 +0100 Subject: [PATCH 05/33] Fix args. --- src/mono/sample/wasm/browser/Program.cs | 2 +- src/mono/sample/wasm/browser/main.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/mono/sample/wasm/browser/Program.cs b/src/mono/sample/wasm/browser/Program.cs index c79d30d1a40507..2adb8ddfc71266 100644 --- a/src/mono/sample/wasm/browser/Program.cs +++ b/src/mono/sample/wasm/browser/Program.cs @@ -11,7 +11,7 @@ public partial class Test { public static int Main(string[] args) { - Console.WriteLine($"MF .NET, args: {String.Join(", ", args)}, location: '{GetLocation()}'"); + Console.WriteLine($"MF .NET, args({args.Length}): {String.Join(", ", args)}, location: '{GetLocation()}'"); return 0; } diff --git a/src/mono/sample/wasm/browser/main.js b/src/mono/sample/wasm/browser/main.js index 786716eaf5ce50..f97a2d4aff45fd 100644 --- a/src/mono/sample/wasm/browser/main.js +++ b/src/mono/sample/wasm/browser/main.js @@ -60,7 +60,7 @@ async function runtime2() { setModuleImports("main.js", { location: { href: () => window.location.href } }); - await dotnet.run(["Runtime 2"]); + await dotnet.withApplicationArguments("Runtime 2").run(); const exports = await getAssemblyExports(getConfig().mainAssemblyName); console.log(exports.Sample.Test.Greet()); From 53f44f328f66136a9e4f1e08dfba8379f71b1d2d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Fi=C5=A1era?= Date: Wed, 9 Nov 2022 11:49:20 +0100 Subject: [PATCH 06/33] Change memory extension so dotnet serve can brotli compress it. --- src/mono/sample/wasm/browser/main.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mono/sample/wasm/browser/main.js b/src/mono/sample/wasm/browser/main.js index f97a2d4aff45fd..be64ecca93a31b 100644 --- a/src/mono/sample/wasm/browser/main.js +++ b/src/mono/sample/wasm/browser/main.js @@ -12,7 +12,7 @@ function exportMemory(memory) { } async function importMemory() { - const response = await fetch("/memory.data"); + const response = await fetch("/memory.dat"); const buffer = await response.arrayBuffer(); return new Int8Array(buffer); } From 31317b9390faa1d5b1ccb578d74f72eeab3ca599 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Fi=C5=A1era?= Date: Wed, 9 Nov 2022 15:38:55 +0100 Subject: [PATCH 07/33] Drop spare assets. --- src/mono/sample/wasm/browser/main.js | 24 ++++-------------------- 1 file changed, 4 insertions(+), 20 deletions(-) diff --git a/src/mono/sample/wasm/browser/main.js b/src/mono/sample/wasm/browser/main.js index be64ecca93a31b..1ae382c896bd9b 100644 --- a/src/mono/sample/wasm/browser/main.js +++ b/src/mono/sample/wasm/browser/main.js @@ -31,26 +31,10 @@ async function runtime2() { const { setModuleImports, getAssemblyExports, getConfig } = await dotnet .withConfig({ mainAssemblyName: "Wasm.Browser.Sample.dll", - assets: [ - { - virtualPath: "runtimeconfig.bin", - behavior: "vfs", - name: "supportFiles/0_runtimeconfig.bin" - }, - { - loadRemote: false, - behavior: "icu", - name: "icudt.dat" - }, - { - virtualPath: "/usr/share/zoneinfo/", - behavior: "vfs", - name: "dotnet.timezones.blat" - }, - { - behavior: "dotnetwasm", - name: "dotnet.wasm" - }], + assets: [{ + behavior: "dotnetwasm", + name: "dotnet.wasm" + }], memory: memory }) .withModuleConfig({ From 772059241763937b5f15db42fb1d66b5e95fdb2e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Fi=C5=A1era?= Date: Mon, 14 Nov 2022 14:27:28 +0100 Subject: [PATCH 08/33] WIP --- src/mono/sample/wasm/browser/Program.cs | 5 ++++- src/mono/sample/wasm/browser/main.js | 15 ++++++++++----- 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/src/mono/sample/wasm/browser/Program.cs b/src/mono/sample/wasm/browser/Program.cs index 2adb8ddfc71266..51f767e61490bc 100644 --- a/src/mono/sample/wasm/browser/Program.cs +++ b/src/mono/sample/wasm/browser/Program.cs @@ -9,9 +9,12 @@ namespace Sample { public partial class Test { + public static readonly DateTime Created = DateTime.Now; + public static readonly string Location = GetLocation(); + public static int Main(string[] args) { - Console.WriteLine($"MF .NET, args({args.Length}): {String.Join(", ", args)}, location: '{GetLocation()}'"); + Console.WriteLine($"MF .NET, args({args.Length}): {String.Join(", ", args)}, static location: '{Location}', location: '{GetLocation()}', created: '{Created.ToString("yyyy-MM-dd HH:mm:ss")}'"); return 0; } diff --git a/src/mono/sample/wasm/browser/main.js b/src/mono/sample/wasm/browser/main.js index 1ae382c896bd9b..06d649d6bac1c2 100644 --- a/src/mono/sample/wasm/browser/main.js +++ b/src/mono/sample/wasm/browser/main.js @@ -1,13 +1,13 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -function exportMemory(memory) { +async function exportMemory(memory) { const blob = new Blob([memory], { type: 'application/octet-stream' }); let donwloadLink = document.createElement('a') donwloadLink.type = 'download' donwloadLink.href = URL.createObjectURL(blob) - donwloadLink.download = 'memory.data' + donwloadLink.download = 'memory.dat' donwloadLink.click() } @@ -19,9 +19,14 @@ async function importMemory() { async function runtime1() { console.log("Runtime 1"); - const dotnet1 = (await import("./dotnet.js?1")).dotnet; - const api1 = await dotnet1.create(); - exportMemory(api1.Module.HEAP8); + const dotnet = (await import("./dotnet.js?1")).dotnet; + const { setModuleImports, getAssemblyExports, getConfig, Module } = await dotnet.create(); + + setModuleImports("main.js", { location: { href: () => window.location.href } }); + + const exports = getAssemblyExports(getConfig().mainAssemblyName); + + await exportMemory(Module.HEAP8); } async function runtime2() { From 17ea00634b92a2cbb81e36fb49339fea6ac8bf16 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Fi=C5=A1era?= Date: Tue, 15 Nov 2022 10:40:46 +0100 Subject: [PATCH 09/33] WIP --- src/mono/sample/wasm/browser/main.js | 34 ++++++++++++++++++++-------- 1 file changed, 24 insertions(+), 10 deletions(-) diff --git a/src/mono/sample/wasm/browser/main.js b/src/mono/sample/wasm/browser/main.js index 06d649d6bac1c2..c021385ff4eefc 100644 --- a/src/mono/sample/wasm/browser/main.js +++ b/src/mono/sample/wasm/browser/main.js @@ -11,9 +11,22 @@ async function exportMemory(memory) { donwloadLink.click() } -async function importMemory() { - const response = await fetch("/memory.dat"); - const buffer = await response.arrayBuffer(); +// async function exportMemory(memory) { // NodeJS +// const require = await import('module').then(mod => mod.createRequire(import.meta.url)); +// const fs = require("fs"); +// fs.promises.writeFile("./memory.dat", memory); +// } + +// async function importMemory() { // Browser +// const response = await fetch("/memory.dat"); +// const buffer = await response.arrayBuffer(); +// return new Int8Array(buffer); +// } + +async function importMemory() { // NodeJS + const require = await import('module').then(mod => mod.createRequire(import.meta.url)); + const fs = require("fs"); + const buffer = await fs.promises.readFile("./memory.dat"); return new Int8Array(buffer); } @@ -51,13 +64,14 @@ async function runtime2() { await dotnet.withApplicationArguments("Runtime 2").run(); - const exports = await getAssemblyExports(getConfig().mainAssemblyName); - console.log(exports.Sample.Test.Greet()); + // const exports = await getAssemblyExports(getConfig().mainAssemblyName); + // console.log(exports.Sample.Test.Greet()); } -const runtimeParam = new URLSearchParams(location.search).get("runtime"); -if (runtimeParam === "1") { - runtime1(); -} else { + +// const runtimeParam = new URLSearchParams(location.search).get("runtime"); +// if (runtimeParam === "1") { + // runtime1(); +// } else { runtime2(); -} \ No newline at end of file +// } \ No newline at end of file From 7318f0a321236b04e0c403abc9f5258389491a5e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Fi=C5=A1era?= Date: Tue, 15 Nov 2022 14:38:55 +0100 Subject: [PATCH 10/33] Split, nodejs. --- .../wasm/browser/Wasm.Browser.Sample.csproj | 4 +++ src/mono/sample/wasm/browser/capture.js | 19 +++++++++++ src/mono/sample/wasm/browser/importNode.js | 34 +++++++++++++++++++ src/mono/sample/wasm/browser/main.js | 29 +++++----------- 4 files changed, 65 insertions(+), 21 deletions(-) create mode 100644 src/mono/sample/wasm/browser/capture.js create mode 100644 src/mono/sample/wasm/browser/importNode.js diff --git a/src/mono/sample/wasm/browser/Wasm.Browser.Sample.csproj b/src/mono/sample/wasm/browser/Wasm.Browser.Sample.csproj index 3d64e4e2c6a72c..594bfaefa3a2a8 100644 --- a/src/mono/sample/wasm/browser/Wasm.Browser.Sample.csproj +++ b/src/mono/sample/wasm/browser/Wasm.Browser.Sample.csproj @@ -5,4 +5,8 @@ false <_SampleProject>Wasm.Browser.Sample.csproj + + + + diff --git a/src/mono/sample/wasm/browser/capture.js b/src/mono/sample/wasm/browser/capture.js new file mode 100644 index 00000000000000..d1a2f1279e758f --- /dev/null +++ b/src/mono/sample/wasm/browser/capture.js @@ -0,0 +1,19 @@ +async function exportMemory(memory) { // NodeJS + const require = await import('module').then(mod => mod.createRequire(import.meta.url)); + const fs = require("fs"); + fs.promises.writeFile("./memory.dat", memory); +} + +async function runtime1() { + console.log("Runtime 1"); + const dotnet = (await import("./dotnet.js?1")).dotnet; + const { setModuleImports, getAssemblyExports, getConfig, Module } = await dotnet.create(); + + setModuleImports("main.js", { location: { href: () => "window.location.href" } }); + + const exports = getAssemblyExports(getConfig().mainAssemblyName); + + await exportMemory(Module.HEAP8); +} + +runtime1(); \ No newline at end of file diff --git a/src/mono/sample/wasm/browser/importNode.js b/src/mono/sample/wasm/browser/importNode.js new file mode 100644 index 00000000000000..e4872063d0e972 --- /dev/null +++ b/src/mono/sample/wasm/browser/importNode.js @@ -0,0 +1,34 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +async function importMemory() { // NodeJS + const require = await import('module').then(mod => mod.createRequire(import.meta.url)); + const fs = require("fs"); + const buffer = await fs.promises.readFile("./memory.dat"); + return new Int8Array(buffer); +} + +async function runtime2() { + console.log("Runtime 2"); + const dotnet = (await import("./dotnet.js?2")).dotnet; + const memory = await importMemory(); + const { setModuleImports, getAssemblyExports, getConfig } = await dotnet + .withConfig({ + mainAssemblyName: "Wasm.Browser.Sample.dll", + assets: [{ + behavior: "dotnetwasm", + name: "dotnet.wasm" + }], + memory: memory + }) + .withModuleConfig({ + configSrc: null + }) + .create(); + + setModuleImports("main.js", { location: { href: () => "window.location.href" } }); + + await dotnet.withApplicationArguments("Runtime 2").run(); +} + +runtime2(); \ No newline at end of file diff --git a/src/mono/sample/wasm/browser/main.js b/src/mono/sample/wasm/browser/main.js index c021385ff4eefc..ce8bf04efe1794 100644 --- a/src/mono/sample/wasm/browser/main.js +++ b/src/mono/sample/wasm/browser/main.js @@ -11,22 +11,9 @@ async function exportMemory(memory) { donwloadLink.click() } -// async function exportMemory(memory) { // NodeJS -// const require = await import('module').then(mod => mod.createRequire(import.meta.url)); -// const fs = require("fs"); -// fs.promises.writeFile("./memory.dat", memory); -// } - -// async function importMemory() { // Browser -// const response = await fetch("/memory.dat"); -// const buffer = await response.arrayBuffer(); -// return new Int8Array(buffer); -// } - -async function importMemory() { // NodeJS - const require = await import('module').then(mod => mod.createRequire(import.meta.url)); - const fs = require("fs"); - const buffer = await fs.promises.readFile("./memory.dat"); +async function importMemory() { + const response = await fetch("/memory.dat"); + const buffer = await response.arrayBuffer(); return new Int8Array(buffer); } @@ -69,9 +56,9 @@ async function runtime2() { } -// const runtimeParam = new URLSearchParams(location.search).get("runtime"); -// if (runtimeParam === "1") { - // runtime1(); -// } else { +const runtimeParam = new URLSearchParams(location.search).get("runtime"); +if (runtimeParam === "1") { + runtime1(); +} else { runtime2(); -// } \ No newline at end of file +} \ No newline at end of file From 8b70764c52bda52e525a4618b622f9d74d838fdb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Fi=C5=A1era?= Date: Wed, 16 Nov 2022 17:15:54 +0100 Subject: [PATCH 11/33] Renames. Initial heap size. --- src/mono/sample/wasm/browser/Wasm.Browser.Sample.csproj | 8 ++++++-- .../sample/wasm/browser/{capture.js => node-capture.js} | 0 .../sample/wasm/browser/{importNode.js => node-import.js} | 0 3 files changed, 6 insertions(+), 2 deletions(-) rename src/mono/sample/wasm/browser/{capture.js => node-capture.js} (100%) rename src/mono/sample/wasm/browser/{importNode.js => node-import.js} (100%) diff --git a/src/mono/sample/wasm/browser/Wasm.Browser.Sample.csproj b/src/mono/sample/wasm/browser/Wasm.Browser.Sample.csproj index 594bfaefa3a2a8..9dd9946741ebd4 100644 --- a/src/mono/sample/wasm/browser/Wasm.Browser.Sample.csproj +++ b/src/mono/sample/wasm/browser/Wasm.Browser.Sample.csproj @@ -2,11 +2,15 @@ true + 0 + 52625408 + <_SampleProject>Wasm.Browser.Sample.csproj - - + + diff --git a/src/mono/sample/wasm/browser/capture.js b/src/mono/sample/wasm/browser/node-capture.js similarity index 100% rename from src/mono/sample/wasm/browser/capture.js rename to src/mono/sample/wasm/browser/node-capture.js diff --git a/src/mono/sample/wasm/browser/importNode.js b/src/mono/sample/wasm/browser/node-import.js similarity index 100% rename from src/mono/sample/wasm/browser/importNode.js rename to src/mono/sample/wasm/browser/node-import.js From 4034401a336d5465fd25b1970347162e0961178e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Fi=C5=A1era?= Date: Thu, 24 Nov 2022 13:35:29 +0100 Subject: [PATCH 12/33] Take the snapshot before user cctors. --- src/mono/sample/wasm/browser/node-capture.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/mono/sample/wasm/browser/node-capture.js b/src/mono/sample/wasm/browser/node-capture.js index d1a2f1279e758f..adc6a8df2391db 100644 --- a/src/mono/sample/wasm/browser/node-capture.js +++ b/src/mono/sample/wasm/browser/node-capture.js @@ -9,9 +9,8 @@ async function runtime1() { const dotnet = (await import("./dotnet.js?1")).dotnet; const { setModuleImports, getAssemblyExports, getConfig, Module } = await dotnet.create(); - setModuleImports("main.js", { location: { href: () => "window.location.href" } }); - - const exports = getAssemblyExports(getConfig().mainAssemblyName); + // setModuleImports("main.js", { location: { href: () => "window.location.href" } }); + // const exports = getAssemblyExports(getConfig().mainAssemblyName); await exportMemory(Module.HEAP8); } From 2e97e553b2bb5de9a1aaf5fdd9ce0d9e89f86e15 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Fi=C5=A1era?= Date: Thu, 24 Nov 2022 14:05:46 +0100 Subject: [PATCH 13/33] Automate taking memory snapshot. --- src/mono/sample/wasm/browser/node-capture.js | 2 +- src/mono/wasm/build/WasmApp.props | 3 ++- src/mono/wasm/build/WasmApp.targets | 17 +++++++++++++++++ 3 files changed, 20 insertions(+), 2 deletions(-) diff --git a/src/mono/sample/wasm/browser/node-capture.js b/src/mono/sample/wasm/browser/node-capture.js index adc6a8df2391db..35a8f209744231 100644 --- a/src/mono/sample/wasm/browser/node-capture.js +++ b/src/mono/sample/wasm/browser/node-capture.js @@ -6,7 +6,7 @@ async function exportMemory(memory) { // NodeJS async function runtime1() { console.log("Runtime 1"); - const dotnet = (await import("./dotnet.js?1")).dotnet; + const dotnet = (await import("./dotnet.js")).dotnet; const { setModuleImports, getAssemblyExports, getConfig, Module } = await dotnet.create(); // setModuleImports("main.js", { location: { href: () => "window.location.href" } }); diff --git a/src/mono/wasm/build/WasmApp.props b/src/mono/wasm/build/WasmApp.props index 89ce6193cacbd3..32af87e1ec2097 100644 --- a/src/mono/wasm/build/WasmApp.props +++ b/src/mono/wasm/build/WasmApp.props @@ -14,7 +14,8 @@ _WasmStripAOTAssemblies; _WasmBuildNativeCore; _WasmGenerateAppBundle; - _AfterWasmBuildApp + _AfterWasmBuildApp; + _WasmMemorySnapshot diff --git a/src/mono/wasm/build/WasmApp.targets b/src/mono/wasm/build/WasmApp.targets index ca37c9ae3ac741..866211c7d7a3a3 100644 --- a/src/mono/wasm/build/WasmApp.targets +++ b/src/mono/wasm/build/WasmApp.targets @@ -418,4 +418,21 @@ + + + + bin/$(Configuration)/AppBundle + wasm-memory-snapshot-capture.js + $(WasmAppBundle)/$(WasmMemorySnapshotCaptureFileName) + + + + + + + + + + + From 4f26a013c2f3155d530d885f6459a9355de5fb77 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Fi=C5=A1era?= Date: Thu, 24 Nov 2022 15:17:03 +0100 Subject: [PATCH 14/33] Use correct AppBundle path. --- src/mono/wasm/build/WasmApp.targets | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/src/mono/wasm/build/WasmApp.targets b/src/mono/wasm/build/WasmApp.targets index 866211c7d7a3a3..57ecf7cbe20329 100644 --- a/src/mono/wasm/build/WasmApp.targets +++ b/src/mono/wasm/build/WasmApp.targets @@ -419,20 +419,21 @@ - + - bin/$(Configuration)/AppBundle - wasm-memory-snapshot-capture.js - $(WasmAppBundle)/$(WasmMemorySnapshotCaptureFileName) + wasm-memory-snapshot-capture.js + memory.dat - + - - - - + + + + From 5f1e453ccf23e7f0cccfd4b7f7a53a63b0a0de3d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Fi=C5=A1era?= Date: Thu, 24 Nov 2022 15:17:24 +0100 Subject: [PATCH 15/33] Use memory snapshots in uni tests. --- src/mono/wasm/test-main.js | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/src/mono/wasm/test-main.js b/src/mono/wasm/test-main.js index 8f37accb9a4bce..637e5ee8368bc6 100644 --- a/src/mono/wasm/test-main.js +++ b/src/mono/wasm/test-main.js @@ -263,6 +263,25 @@ async function run() { .withExitCodeLogging() .withElementOnExit(); + const response = await fetch("/memory.dat"); + const buffer = await response.arrayBuffer(); + const memory = new Int8Array(buffer); + + console.log("Memory snapshot loaded"); + + dotnet + .withConfig({ + mainAssemblyName: "WasmTestRunner.dll", + assets: [{ + behavior: "dotnetwasm", + name: "dotnet.wasm" + }], + memory: memory + }) + .withModuleConfig({ + configSrc: null + }); + if (is_node) { dotnet .withEnvironmentVariable("NodeJSPlatform", process.platform) From 594cb9d033471fe07b7d6c51cf1c8962c7e1575b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Fi=C5=A1era?= Date: Fri, 25 Nov 2022 13:30:43 +0100 Subject: [PATCH 16/33] Clean up. --- src/mono/wasm/runtime/startup.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mono/wasm/runtime/startup.ts b/src/mono/wasm/runtime/startup.ts index 8f0db8230b8e94..74e5fda6bb24fe 100644 --- a/src/mono/wasm/runtime/startup.ts +++ b/src/mono/wasm/runtime/startup.ts @@ -69,7 +69,7 @@ export function configure_emscripten_startup(module: DotnetModule, exportedAPI: // execution order == [2] == module.preRun = [() => preRunAsync(userPreRun)]; // execution order == [4] == - module.onRuntimeInitialized = () => { console.log("MF onInit"); onRuntimeInitializedAsync(userOnRuntimeInitialized); }; + module.onRuntimeInitialized = () => { onRuntimeInitializedAsync(userOnRuntimeInitialized); }; // execution order == [5] == module.postRun = [() => postRunAsync(userpostRun)]; // execution order == [6] == From c8742ffe8cec3e03357ec75aa162ab1f126308ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Fi=C5=A1era?= Date: Fri, 25 Nov 2022 13:48:15 +0100 Subject: [PATCH 17/33] Dummy fetch polyfill for V8. --- src/mono/wasm/test-main.js | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/src/mono/wasm/test-main.js b/src/mono/wasm/test-main.js index 637e5ee8368bc6..35ebe391bf773f 100644 --- a/src/mono/wasm/test-main.js +++ b/src/mono/wasm/test-main.js @@ -243,6 +243,20 @@ const App = { }; globalThis.App = App; // Necessary as System.Runtime.InteropServices.JavaScript.Tests.MarshalTests (among others) call the App.call_test_method directly +if (typeof (globalThis.fetch) !== "function" && typeof (read) === "function") { + // note that it can't open files with unicode names, like Strae.xml + // https://bugs.chromium.org/p/v8/issues/detail?id=12541 + globalThis.fetch = async (url) => { + const arrayBuffer = new Uint8Array(read(url, "binary")); + return { + ok: true, + url, + arrayBuffer: () => arrayBuffer, + json: () => { throw new Error("Dummy fetch doesn't support json"); } + }; + } +} + async function run() { try { const { dotnet, exit, INTERNAL } = await loadDotnet('./dotnet.js'); @@ -263,7 +277,7 @@ async function run() { .withExitCodeLogging() .withElementOnExit(); - const response = await fetch("/memory.dat"); + const response = await fetch("./memory.dat"); const buffer = await response.arrayBuffer(); const memory = new Int8Array(buffer); From ab8857253d290abd45ec431b79fbe1be4f793556 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Fi=C5=A1era?= Date: Tue, 29 Nov 2022 10:42:35 +0100 Subject: [PATCH 18/33] Use node from emsdk. --- src/mono/wasm/build/WasmApp.Native.targets | 22 +++++++++++++++++++++- src/mono/wasm/build/WasmApp.targets | 18 ------------------ 2 files changed, 21 insertions(+), 19 deletions(-) diff --git a/src/mono/wasm/build/WasmApp.Native.targets b/src/mono/wasm/build/WasmApp.Native.targets index 6f9524e7f7a30e..b63cc0132bb523 100644 --- a/src/mono/wasm/build/WasmApp.Native.targets +++ b/src/mono/wasm/build/WasmApp.Native.targets @@ -58,13 +58,15 @@ $([MSBuild]::NormalizeDirectory($(EmscriptenSdkToolsPath))) $([MSBuild]::NormalizeDirectory($(EmscriptenNodeToolsPath))) $([MSBuild]::NormalizeDirectory($(EmscriptenUpstreamBinPath))) + + $([MSBuild]::NormalizePath($(EmscriptenNodeToolsPath), 'bin', 'node$(_ExeExt)')) - + @@ -657,4 +659,22 @@ + + + + wasm-memory-snapshot-capture.js + memory.dat + + + + + + + + + + + diff --git a/src/mono/wasm/build/WasmApp.targets b/src/mono/wasm/build/WasmApp.targets index 57ecf7cbe20329..ca37c9ae3ac741 100644 --- a/src/mono/wasm/build/WasmApp.targets +++ b/src/mono/wasm/build/WasmApp.targets @@ -418,22 +418,4 @@ - - - - wasm-memory-snapshot-capture.js - memory.dat - - - - - - - - - - - From 1a632518c0908678c113107a737c747fb4a9157a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Fi=C5=A1era?= Date: Tue, 29 Nov 2022 14:25:37 +0100 Subject: [PATCH 19/33] Cleanup sample. --- src/mono/sample/wasm/browser/Program.cs | 4 +- src/mono/sample/wasm/browser/main.js | 83 +++++++------------------ 2 files changed, 24 insertions(+), 63 deletions(-) diff --git a/src/mono/sample/wasm/browser/Program.cs b/src/mono/sample/wasm/browser/Program.cs index 51f767e61490bc..474aa912b09e9d 100644 --- a/src/mono/sample/wasm/browser/Program.cs +++ b/src/mono/sample/wasm/browser/Program.cs @@ -14,7 +14,7 @@ public partial class Test public static int Main(string[] args) { - Console.WriteLine($"MF .NET, args({args.Length}): {String.Join(", ", args)}, static location: '{Location}', location: '{GetLocation()}', created: '{Created.ToString("yyyy-MM-dd HH:mm:ss")}'"); + Console.WriteLine($".NET, args({args.Length}): {String.Join(", ", args)}, static location: '{Location}', location: '{GetLocation()}', created: '{Created.ToString("yyyy-MM-dd HH:mm:ss")}'"); return 0; } @@ -22,6 +22,6 @@ public static int Main(string[] args) internal static partial string GetLocation(); [JSExport] - internal static string Greet() => "MF JSExport"; + internal static string Greet() => "JSExport"; } } diff --git a/src/mono/sample/wasm/browser/main.js b/src/mono/sample/wasm/browser/main.js index ce8bf04efe1794..c874bb03bece96 100644 --- a/src/mono/sample/wasm/browser/main.js +++ b/src/mono/sample/wasm/browser/main.js @@ -1,64 +1,25 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -async function exportMemory(memory) { - const blob = new Blob([memory], { type: 'application/octet-stream' }); - - let donwloadLink = document.createElement('a') - donwloadLink.type = 'download' - donwloadLink.href = URL.createObjectURL(blob) - donwloadLink.download = 'memory.dat' - donwloadLink.click() -} - -async function importMemory() { - const response = await fetch("/memory.dat"); - const buffer = await response.arrayBuffer(); - return new Int8Array(buffer); -} - -async function runtime1() { - console.log("Runtime 1"); - const dotnet = (await import("./dotnet.js?1")).dotnet; - const { setModuleImports, getAssemblyExports, getConfig, Module } = await dotnet.create(); - - setModuleImports("main.js", { location: { href: () => window.location.href } }); - - const exports = getAssemblyExports(getConfig().mainAssemblyName); - - await exportMemory(Module.HEAP8); -} - -async function runtime2() { - console.log("Runtime 2"); - const dotnet = (await import("./dotnet.js?2")).dotnet; - const memory = await importMemory(); - const { setModuleImports, getAssemblyExports, getConfig } = await dotnet - .withConfig({ - mainAssemblyName: "Wasm.Browser.Sample.dll", - assets: [{ - behavior: "dotnetwasm", - name: "dotnet.wasm" - }], - memory: memory - }) - .withModuleConfig({ - configSrc: null - }) - .create(); - - setModuleImports("main.js", { location: { href: () => window.location.href } }); - - await dotnet.withApplicationArguments("Runtime 2").run(); - - // const exports = await getAssemblyExports(getConfig().mainAssemblyName); - // console.log(exports.Sample.Test.Greet()); -} - - -const runtimeParam = new URLSearchParams(location.search).get("runtime"); -if (runtimeParam === "1") { - runtime1(); -} else { - runtime2(); -} \ No newline at end of file +const dotnet = (await import("./dotnet.js")).dotnet; +const memory = await importMemory(); +const { setModuleImports, getAssemblyExports, getConfig } = await dotnet + .withConfig({ + mainAssemblyName: "Wasm.Browser.Sample.dll", + assets: [{ + behavior: "dotnetwasm", + name: "dotnet.wasm" + }], + memory: memory + }) + .withModuleConfig({ + configSrc: null + }) + .create(); + +setModuleImports("main.js", { location: { href: () => window.location.href } }); + +await dotnet.withApplicationArguments("Runtime 2").run(); + +const exports = await getAssemblyExports("Wasm.Browser.Sample.dll"); +console.log(exports.Sample.Test.Gree()); \ No newline at end of file From bb43b98dab6024a40d1d70c537cc1a0144e3255c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Fi=C5=A1era?= Date: Tue, 29 Nov 2022 14:53:01 +0100 Subject: [PATCH 20/33] Support for built-in memory. --- .../sample/wasm/browser/Wasm.Browser.Sample.csproj | 2 +- src/mono/sample/wasm/browser/main.js | 13 ++++++++++--- src/mono/wasm/runtime/polyfills.ts | 2 +- src/mono/wasm/runtime/types.ts | 2 +- 4 files changed, 13 insertions(+), 6 deletions(-) diff --git a/src/mono/sample/wasm/browser/Wasm.Browser.Sample.csproj b/src/mono/sample/wasm/browser/Wasm.Browser.Sample.csproj index 9dd9946741ebd4..cfe9356f4e6521 100644 --- a/src/mono/sample/wasm/browser/Wasm.Browser.Sample.csproj +++ b/src/mono/sample/wasm/browser/Wasm.Browser.Sample.csproj @@ -3,7 +3,7 @@ true 0 - 52625408 + 105250816 diff --git a/src/mono/sample/wasm/browser/main.js b/src/mono/sample/wasm/browser/main.js index c874bb03bece96..5315e261bac965 100644 --- a/src/mono/sample/wasm/browser/main.js +++ b/src/mono/sample/wasm/browser/main.js @@ -1,9 +1,16 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -const dotnet = (await import("./dotnet.js")).dotnet; +import { dotnet } from "./dotnet.js" + +async function importMemory() { + const response = await fetch("./memory.dat"); + const buffer = await response.arrayBuffer(); + return new Int8Array(buffer); +} + const memory = await importMemory(); -const { setModuleImports, getAssemblyExports, getConfig } = await dotnet +const { setModuleImports, getAssemblyExports } = await dotnet .withConfig({ mainAssemblyName: "Wasm.Browser.Sample.dll", assets: [{ @@ -22,4 +29,4 @@ setModuleImports("main.js", { location: { href: () => window.location.href } }); await dotnet.withApplicationArguments("Runtime 2").run(); const exports = await getAssemblyExports("Wasm.Browser.Sample.dll"); -console.log(exports.Sample.Test.Gree()); \ No newline at end of file +console.log(exports.Sample.Test.Greet()); \ No newline at end of file diff --git a/src/mono/wasm/runtime/polyfills.ts b/src/mono/wasm/runtime/polyfills.ts index c17081c5dc75ce..988f25ecd1dfa4 100644 --- a/src/mono/wasm/runtime/polyfills.ts +++ b/src/mono/wasm/runtime/polyfills.ts @@ -186,7 +186,7 @@ export function init_polyfills(replacements: EarlyReplacements): void { originalUpdateGlobalBufferAndViews(buffer); afterUpdateGlobalBufferAndViews(buffer); - if (runtimeHelpers.config.memory) { + if (runtimeHelpers.config.memory && runtimeHelpers.config.memory !== true) { Module.HEAP8.set(runtimeHelpers.config.memory, 0); } }; diff --git a/src/mono/wasm/runtime/types.ts b/src/mono/wasm/runtime/types.ts index dc4ae1237ed4b5..3d42cec0cd0119 100644 --- a/src/mono/wasm/runtime/types.ts +++ b/src/mono/wasm/runtime/types.ts @@ -115,7 +115,7 @@ export type MonoConfig = { * initial number of workers to add to the emscripten pthread pool */ pthreadPoolSize?: number, - memory?: Int8Array, + memory?: Int8Array | boolean, }; export type MonoConfigInternal = MonoConfig & { From 2a4d7109ceefb15d7dcd7c732c02ab6bd3a96f26 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Fi=C5=A1era?= Date: Tue, 29 Nov 2022 14:54:48 +0100 Subject: [PATCH 21/33] Disable advanced sample on CI. --- src/libraries/tests.proj | 1 + 1 file changed, 1 insertion(+) diff --git a/src/libraries/tests.proj b/src/libraries/tests.proj index 8b6a516050e97b..e471930deb800a 100644 --- a/src/libraries/tests.proj +++ b/src/libraries/tests.proj @@ -39,6 +39,7 @@ + From f6db54eae80ce2fd81e2990a59f0e75da47c6614 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Fi=C5=A1era?= Date: Wed, 30 Nov 2022 11:00:45 +0100 Subject: [PATCH 22/33] Use INITIAL_MEMORY in wasm.proj --- src/mono/wasm/test-main.js | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/src/mono/wasm/test-main.js b/src/mono/wasm/test-main.js index 35ebe391bf773f..062678a090b318 100644 --- a/src/mono/wasm/test-main.js +++ b/src/mono/wasm/test-main.js @@ -257,6 +257,19 @@ if (typeof (globalThis.fetch) !== "function" && typeof (read) === "function") { } } +async function importMemory() { + if (is_node) { + const require = await import('module').then(mod => mod.createRequire(import.meta.url)); + const fs = require("fs"); + const buffer = await fs.promises.readFile("./memory.dat"); + return new Int8Array(buffer); + } + + const response = await fetch("./memory.dat"); + const buffer = await response.arrayBuffer(); + return new Int8Array(buffer); +} + async function run() { try { const { dotnet, exit, INTERNAL } = await loadDotnet('./dotnet.js'); @@ -277,9 +290,7 @@ async function run() { .withExitCodeLogging() .withElementOnExit(); - const response = await fetch("./memory.dat"); - const buffer = await response.arrayBuffer(); - const memory = new Int8Array(buffer); + const memory = await importMemory(); console.log("Memory snapshot loaded"); From 752517e2913cbe7e4023ad9582fabd5ce8c53565 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Fi=C5=A1era?= Date: Wed, 30 Nov 2022 14:10:46 +0100 Subject: [PATCH 23/33] Use wa-edit to medge snapshot into dotnet.wasm --- src/mono/sample/wasm/browser/main.js | 11 ++--------- src/mono/wasm/build/WasmApp.Native.targets | 7 +++++-- src/mono/wasm/test-main.js | 19 +------------------ 3 files changed, 8 insertions(+), 29 deletions(-) diff --git a/src/mono/sample/wasm/browser/main.js b/src/mono/sample/wasm/browser/main.js index 5315e261bac965..3a39f42216a45b 100644 --- a/src/mono/sample/wasm/browser/main.js +++ b/src/mono/sample/wasm/browser/main.js @@ -3,13 +3,6 @@ import { dotnet } from "./dotnet.js" -async function importMemory() { - const response = await fetch("./memory.dat"); - const buffer = await response.arrayBuffer(); - return new Int8Array(buffer); -} - -const memory = await importMemory(); const { setModuleImports, getAssemblyExports } = await dotnet .withConfig({ mainAssemblyName: "Wasm.Browser.Sample.dll", @@ -17,7 +10,7 @@ const { setModuleImports, getAssemblyExports } = await dotnet behavior: "dotnetwasm", name: "dotnet.wasm" }], - memory: memory + memory: true }) .withModuleConfig({ configSrc: null @@ -26,7 +19,7 @@ const { setModuleImports, getAssemblyExports } = await dotnet setModuleImports("main.js", { location: { href: () => window.location.href } }); -await dotnet.withApplicationArguments("Runtime 2").run(); +await dotnet.withApplicationArguments("Single file .NET").run(); const exports = await getAssemblyExports("Wasm.Browser.Sample.dll"); console.log(exports.Sample.Test.Greet()); \ No newline at end of file diff --git a/src/mono/wasm/build/WasmApp.Native.targets b/src/mono/wasm/build/WasmApp.Native.targets index b63cc0132bb523..164b61d61362da 100644 --- a/src/mono/wasm/build/WasmApp.Native.targets +++ b/src/mono/wasm/build/WasmApp.Native.targets @@ -672,9 +672,12 @@ + - - + + + + diff --git a/src/mono/wasm/test-main.js b/src/mono/wasm/test-main.js index 062678a090b318..967696bb32fa0e 100644 --- a/src/mono/wasm/test-main.js +++ b/src/mono/wasm/test-main.js @@ -257,19 +257,6 @@ if (typeof (globalThis.fetch) !== "function" && typeof (read) === "function") { } } -async function importMemory() { - if (is_node) { - const require = await import('module').then(mod => mod.createRequire(import.meta.url)); - const fs = require("fs"); - const buffer = await fs.promises.readFile("./memory.dat"); - return new Int8Array(buffer); - } - - const response = await fetch("./memory.dat"); - const buffer = await response.arrayBuffer(); - return new Int8Array(buffer); -} - async function run() { try { const { dotnet, exit, INTERNAL } = await loadDotnet('./dotnet.js'); @@ -290,10 +277,6 @@ async function run() { .withExitCodeLogging() .withElementOnExit(); - const memory = await importMemory(); - - console.log("Memory snapshot loaded"); - dotnet .withConfig({ mainAssemblyName: "WasmTestRunner.dll", @@ -301,7 +284,7 @@ async function run() { behavior: "dotnetwasm", name: "dotnet.wasm" }], - memory: memory + memory: true }) .withModuleConfig({ configSrc: null From d3fe16f923f2c4abd52e741e64b9ff6b117b8e57 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Fi=C5=A1era?= Date: Wed, 30 Nov 2022 17:13:08 +0100 Subject: [PATCH 24/33] Update wa-edit path for CI. --- src/mono/wasm/build/WasmApp.Native.targets | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/mono/wasm/build/WasmApp.Native.targets b/src/mono/wasm/build/WasmApp.Native.targets index 164b61d61362da..90fcbbaf74b0df 100644 --- a/src/mono/wasm/build/WasmApp.Native.targets +++ b/src/mono/wasm/build/WasmApp.Native.targets @@ -666,6 +666,8 @@ wasm-memory-snapshot-capture.js memory.dat + wa-edit + ~/.dotnet/tools/wa-edit @@ -675,7 +677,7 @@ - + From b17bfaa1bb2d75c428983d0e0034a9d13a01fb88 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Fi=C5=A1era?= Date: Thu, 1 Dec 2022 10:06:34 +0100 Subject: [PATCH 25/33] One more round on how to use dotnet tool --- src/mono/wasm/build/WasmApp.Native.targets | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/mono/wasm/build/WasmApp.Native.targets b/src/mono/wasm/build/WasmApp.Native.targets index 90fcbbaf74b0df..448de3e613a4f9 100644 --- a/src/mono/wasm/build/WasmApp.Native.targets +++ b/src/mono/wasm/build/WasmApp.Native.targets @@ -666,8 +666,7 @@ wasm-memory-snapshot-capture.js memory.dat - wa-edit - ~/.dotnet/tools/wa-edit + $(DotNetTool) wa-edit From bfdd2fbd02c4323cdd61922906e7e6854f0c356e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Fi=C5=A1era?= Date: Mon, 5 Dec 2022 12:45:21 +0100 Subject: [PATCH 26/33] Rewrite wa-edit as temporal msbuild task. --- src/mono/wasm/build/WasmApp.Native.targets | 5 +- .../AppendMemorySnapshotToWasmFile.cs | 701 ++++++++++++++++++ 2 files changed, 703 insertions(+), 3 deletions(-) create mode 100644 src/tasks/WasmAppBuilder/AppendMemorySnapshotToWasmFile.cs diff --git a/src/mono/wasm/build/WasmApp.Native.targets b/src/mono/wasm/build/WasmApp.Native.targets index 448de3e613a4f9..0e68a6e78b5f3e 100644 --- a/src/mono/wasm/build/WasmApp.Native.targets +++ b/src/mono/wasm/build/WasmApp.Native.targets @@ -2,6 +2,7 @@ + @@ -666,17 +667,15 @@ wasm-memory-snapshot-capture.js memory.dat - $(DotNetTool) wa-edit - - + diff --git a/src/tasks/WasmAppBuilder/AppendMemorySnapshotToWasmFile.cs b/src/tasks/WasmAppBuilder/AppendMemorySnapshotToWasmFile.cs new file mode 100644 index 00000000000000..a0ba00c1c3708d --- /dev/null +++ b/src/tasks/WasmAppBuilder/AppendMemorySnapshotToWasmFile.cs @@ -0,0 +1,701 @@ +// 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.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.IO; +using System.Linq; +using Microsoft.Build.Framework; +using Microsoft.Build.Utilities; +using WebAssemblyInfo; + +public class AppendMemorySnapshotToWasmFile : Task +{ + [Required, NotNull] + public string Source { get; set; } = default!; + + [Required, NotNull] + public string Destination { get; set; } = default!; + + [Required, NotNull] + public string DataSectionFile { get; set; } = default!; + + public bool Verbose { get; set; } + + public override bool Execute() + { + using var reader = new WasmRewriter(Source, Destination, DataSectionFile, 0, DataMode.Active, Verbose); + reader.Parse(); + + return true; + } +} + +#region wa-edit + +namespace WebAssemblyInfo +{ + internal sealed class WasmRewriter : WasmReaderBase, IDisposable + { + private readonly string DestinationPath; + private readonly string _dataSectionFile; + private readonly int _dataOffset; + private readonly DataMode _dataSectionMode; + private readonly bool _verbose; + private BinaryWriter Writer; + + public WasmRewriter(string source, string destination, string dataSectionFile, int dataOffset, DataMode dataSectionMode, bool verbose) : base(source, verbose) + { + if (_verbose) + Console.WriteLine($"Writing wasm file: {destination}"); + + DestinationPath = destination; + _dataSectionFile = dataSectionFile; + _dataOffset = dataOffset; + _dataSectionMode = dataSectionMode; + _verbose = verbose; + var stream = File.Open(DestinationPath, FileMode.Create); + Writer = new BinaryWriter(stream); + } + + public void Dispose() + { + Writer.Dispose(); + Reader.Dispose(); + } + + protected override void ReadModule() + { + Writer.Write(MagicWasm); + Writer.Write(1); // Version + + base.ReadModule(); + + Writer.BaseStream.Position = MagicWasm.Length; + Writer.Write(Version); + } + + protected override void ReadSection(SectionInfo section) + { + if (File.Exists(_dataSectionFile)) + { + if (section.id == SectionId.Data) + { + RewriteDataSection(); + return; + } + + if (section.id == SectionId.DataCount) + { + // omit DataCount section for now, it is not needed + return; + } + } + + WriteSection(section); + } + + private void WriteSection(SectionInfo section) + { + Reader.BaseStream.Seek(section.offset, SeekOrigin.Begin); + Writer.Write(Reader.ReadBytes((int)section.size + (int)(section.begin - section.offset))); + } + + private struct Chunk + { + public int index, size; + } + + private List Split(byte[] data) + { + int zeroesLen = 9; + var list = new List(); + var span = new ReadOnlySpan(data); + var zeroes = new ReadOnlySpan(new byte[zeroesLen]); + int offset = 0; + int stripped = 0; + + do + { + int index = span.IndexOf(zeroes); + if (index == -1) + { + if (_verbose) + Console.WriteLine($" add last idx: {offset} size: {data.Length - offset} span remaining len: {span.Length}"); + + list.Add(new Chunk { index = offset, size = data.Length - offset }); + return list; + } + if (index != 0) + { + if (_verbose) + Console.WriteLine($" add idx: {offset} size: {index} span remaining len: {span.Length} span index: {index}"); + + list.Add(new Chunk { index = offset, size = index }); + span = span.Slice(index + zeroesLen); + offset += index + zeroesLen; + stripped += zeroesLen; + } + + index = -1; + for (int i = 0; i < span.Length; i++) + { + if (span[i] != (byte)0) + { + index = i; + break; + } + } + + if (index == -1) + { + stripped += data.Length - offset; + break; + } + + //Console.WriteLine($"skip: {index}"); + if (index != 0) + { + span = span.Slice(index); + offset += index; + stripped += index; + } + } while (true); + + if (_verbose) + Console.Write($" segments detected: {list.Count:N0} zero bytes stripped: {stripped:N0}"); + + return list; + } + + private void RewriteDataSection() + { + //var oo = Writer.BaseStream.Position; + var bytes = File.ReadAllBytes(_dataSectionFile); + var segments = Split(bytes); + + var mode = _dataSectionMode; + var sectionLen = U32Len((uint)segments.Count); + foreach (var segment in segments) + sectionLen += GetDataSegmentLength(mode, segment, _dataOffset + segment.index); + + // section beginning + Writer.Write((byte)SectionId.Data); + WriteU32(sectionLen); + + // section content + WriteU32((uint)segments.Count); + foreach (var segment in segments) + WriteDataSegment(mode, bytes, segment, _dataOffset + segment.index); + + //var pos = Writer.BaseStream.Position; + //Writer.BaseStream.Position = oo; + //DumpBytes(64); + //Writer.BaseStream.Position = pos; + } + + private static uint GetDataSegmentLength(DataMode mode, Chunk chunk, int memoryOffset) + { + var len = U32Len((uint)mode) + U32Len((uint)chunk.size) + (uint)chunk.size; + if (mode == DataMode.Active) + len += WasmRewriter.ConstI32ExprLen(memoryOffset); + + return len; + } + + private void WriteDataSegment(DataMode mode, byte[] data, Chunk chunk, int memoryOffset) + { + // data segment + WriteU32((uint)mode); + + if (mode == DataMode.Active) + WriteConstI32Expr(memoryOffset); + + WriteU32((uint)chunk.size); + Writer.Write(data, chunk.index, chunk.size); + } + + public void DumpBytes(int count) + { + var pos = Writer.BaseStream.Position; + Console.WriteLine("bytes"); + + for (int i = 0; i < count; i++) + { + Console.Write($" {Writer.BaseStream.ReadByte():x}"); + } + + Console.WriteLine(); + Writer.BaseStream.Position = pos; + } + + private static uint ConstI32ExprLen(int cn) + { + return 2 + I32Len(cn); + } + + // i32.const + private void WriteConstI32Expr(int cn) + { + Writer.Write((byte)Opcode.I32_Const); + WriteI32(cn); + Writer.Write((byte)Opcode.End); + } + + public void WriteU32(uint n) + { + do + { + byte b = (byte)(n & 0x7f); + n >>= 7; + if (n != 0) + b |= 0x80; + Writer.Write(b); + } while (n != 0); + } + + public static uint U32Len(uint n) + { + uint len = 0u; + do + { + n >>= 7; + len++; + } while (n != 0); + + return len; + } + + public void WriteI32(int n) + { + var final = false; + do + { + byte b = (byte)(n & 0x7f); + n >>= 7; + + if ((n == 0 && ((n & 0x80000000) == 0)) || (n == -1 && ((n & 0x80000000) == 0x80))) + final = true; + else + b |= 0x80; + + Writer.Write(b); + } while (!final); + } + + public static uint I32Len(int n) + { + var final = false; + var len = 0u; + do + { + n >>= 7; + + if ((n == 0 && ((n & 0x80000000) == 0)) || (n == -1 && ((n & 0x80000000) == 0x80))) + final = true; + + len++; + } while (!final); + + return len; + } + } + + public abstract class WasmReaderBase + { + public readonly BinaryReader Reader; + private readonly bool _verbose; + + public uint Version { get; private set; } + public string Path { get; private set; } + + public WasmReaderBase(string path, bool verbose) + { + if (_verbose) + Console.WriteLine($"Reading wasm file: {path}"); + + Path = path; + _verbose = verbose; + var stream = File.Open(Path, FileMode.Open); + Reader = new BinaryReader(stream); + } + + public void Parse() + { + ReadModule(); + } + + protected byte[] MagicWasm = { 0x0, 0x61, 0x73, 0x6d }; + + protected virtual void ReadModule() + { + var magicBytes = Reader.ReadBytes(4); + + for (int i = 0; i < MagicWasm.Length; i++) + { + if (MagicWasm[i] != magicBytes[i]) + throw new FileLoadException("not wasm file, module magic is wrong"); + } + + Version = Reader.ReadUInt32(); + if (_verbose) + Console.WriteLine($"WebAssembly binary format version: {Version}"); + + while (Reader.BaseStream.Position < Reader.BaseStream.Length) + ReadSection(); + } + + protected enum SectionId + { + Custom = 0, + Type, + Import, + Function, + Table, + Memory, + Global, + Export, + Start, + Element, + Code, + Data, + DataCount, + Tag, + } + + protected struct SectionInfo + { + public SectionId id; + public uint size; + public long offset; + public long begin; + } + protected List sections = new(); + protected Dictionary> sectionsById = new(); + + protected abstract void ReadSection(SectionInfo section); + + private void ReadSection() + { + var section = new SectionInfo() { offset = Reader.BaseStream.Position, id = (SectionId)Reader.ReadByte(), size = ReadU32(), begin = Reader.BaseStream.Position }; + sections.Add(section); + if (!sectionsById.ContainsKey(section.id)) + sectionsById[section.id] = new List(); + + sectionsById[section.id].Add(section); + + if (_verbose) + Console.Write($"Reading section: {section.id,9} size: {section.size,12}"); + + ReadSection(section); + + if (_verbose) + Console.WriteLine(); + + Reader.BaseStream.Seek(section.begin + section.size, SeekOrigin.Begin); + } + + public uint ReadU32() + { + uint value = 0; + var offset = 0; + do + { + var b = Reader.ReadByte(); + value |= (uint)(b & 0x7f) << offset; + + if ((b & 0x80) == 0) + break; + + offset += 7; + } while (true); + + return value; + } + + protected int ReadI32() + { + int value = 0; + var offset = 0; + byte b; + + do + { + b = Reader.ReadByte(); + value |= (int)(b & 0x7f) << offset; + + if ((b & 0x80) == 0) + break; + + offset += 7; + } while (true); + + if (offset < 32 && (b & 0x40) == 0x40) + value |= (~(int)0 << offset); + + return value; + } + + protected long ReadI64() + { + long value = 0; + var offset = 0; + byte b; + + do + { + b = Reader.ReadByte(); + value |= (long)(b & 0x7f) << offset; + + if ((b & 0x80) == 0) + break; + + offset += 7; + } while (true); + + if (offset < 64 && (b & 0x40) == 0x40) + value |= (~(long)0 << offset); + + return value; + } + } + + public enum DataMode + { + Active = 0, + Passive = 1, + ActiveMemory = 2, + } + + internal enum Opcode : byte + { + // control + Unreachable = 0x00, + Nop = 0x01, + Block = 0x02, + Loop = 0x03, + If = 0x04, + Else = 0x05, + Try = 0x06, + Catch = 0x07, + Throw = 0x08, + Rethrow = 0x09, + End = 0x0b, + Br = 0x0c, + Br_If = 0x0d, + Br_Table = 0x0e, + Return = 0x0f, + Call = 0x10, + Call_Indirect = 0x11, + Delegate = 0x18, + Catch_All = 0x19, + // reference + Ref_Null = 0xd0, + Ref_Is_Null = 0xd1, + Ref_Func = 0xd2, + // parametric + Drop = 0x1a, + Select = 0x1b, + Select_Vec = 0x1c, + // variable + Local_Get = 0x20, + Local_Set = 0x21, + Local_Tee = 0x22, + Global_Get = 0x23, + Global_Set = 0x24, + // table + Table_Get = 0x25, + Table_Set = 0x26, + // memory + I32_Load = 0x28, + I64_Load = 0x29, + F32_Load = 0x2a, + F64_Load = 0x2b, + I32_Load8_S = 0x2c, + I32_Load8_U = 0x2d, + I32_Load16_S = 0x2e, + I32_Load16_U = 0x2f, + I64_Load8_S = 0x30, + I64_Load8_U = 0x31, + I64_Load16_S = 0x32, + I64_Load16_U = 0x33, + I64_Load32_S = 0x34, + I64_Load32_U = 0x35, + I32_Store = 0x36, + I64_Store = 0x37, + F32_Store = 0x38, + F64_Store = 0x39, + I32_Store8 = 0x3a, + I32_Store16 = 0x3b, + I64_Store8 = 0x3c, + I64_Store16 = 0x3d, + I64_Store32 = 0x3e, + Memory_Size = 0x3f, + Memory_Grow = 0x40, + // numeric + I32_Const = 0x41, + I64_Const = 0x42, + F32_Const = 0x43, + F64_Const = 0x44, + I32_Eqz = 0x45, + I32_Eq = 0x46, + I32_Ne = 0x47, + I32_Lt_S = 0x48, + I32_Lt_U = 0x49, + I32_Gt_S = 0x4a, + I32_Gt_U = 0x4b, + I32_Le_S = 0x4c, + I32_Le_U = 0x4d, + I32_Ge_S = 0x4e, + I32_Ge_U = 0x4f, + I64_Eqz = 0x50, + I64_Eq = 0x51, + I64_Ne = 0x52, + I64_Lt_S = 0x53, + I64_Lt_U = 0x54, + I64_Gt_S = 0x55, + I64_Gt_U = 0x56, + I64_Le_S = 0x57, + I64_Le_U = 0x58, + I64_Ge_S = 0x59, + I64_Ge_U = 0x5a, + F32_Eq = 0x5b, + F32_Ne = 0x5c, + F32_Lt = 0x5d, + F32_Gt = 0x5e, + F32_Le = 0x5f, + F32_Ge = 0x60, + F64_Eq = 0x61, + F64_Ne = 0x62, + F64_Lt = 0x63, + F64_Gt = 0x64, + F64_Le = 0x65, + F64_Ge = 0x66, + I32_Clz = 0x67, + I32_Ctz = 0x68, + I32_Popcnt = 0x69, + I32_Add = 0x6a, + I32_Sub = 0x6b, + I32_Mul = 0x6c, + I32_Div_S = 0x6d, + I32_Div_U = 0x6e, + I32_Rem_S = 0x6f, + I32_Rem_U = 0x70, + I32_And = 0x71, + I32_Or = 0x72, + I32_Xor = 0x73, + I32_Shl = 0x74, + I32_Shr_S = 0x75, + I32_Shr_U = 0x76, + I32_Rotl = 0x77, + I32_Rotr = 0x78, + I64_Clz = 0x79, + I64_Ctz = 0x7a, + I64_Popcnt = 0x7b, + I64_Add = 0x7c, + I64_Sub = 0x7d, + I64_Mul = 0x7e, + I64_Div_S = 0x7f, + I64_Div_U = 0x80, + I64_Rem_S = 0x81, + I64_Rem_U = 0x82, + I64_And = 0x83, + I64_Or = 0x84, + I64_Xor = 0x85, + I64_Shl = 0x86, + I64_Shr_S = 0x87, + I64_Shr_U = 0x88, + I64_Rotl = 0x89, + I64_Rotr = 0x8a, + F32_Abs = 0x8b, + F32_Neg = 0x8c, + F32_Ceil = 0x8d, + F32_Floor = 0x8e, + F32_Trunc = 0x8f, + F32_Nearest = 0x90, + F32_Sqrt = 0x91, + F32_Add = 0x92, + F32_Sub = 0x93, + F32_Mul = 0x94, + F32_Div = 0x95, + F32_Min = 0x96, + F32_Max = 0x97, + F32_Copysign = 0x98, + F64_Abs = 0x99, + F64_Neg = 0x9a, + F64_Ceil = 0x9b, + F64_Floor = 0x9c, + F64_Trunc = 0x9d, + F64_Nearest = 0x9e, + F64_Sqrt = 0x9f, + F64_Add = 0xa0, + F64_Sub = 0xa1, + F64_Mul = 0xa2, + F64_Div = 0xa3, + F64_Min = 0xa4, + F64_Max = 0xa5, + F64_Copysign = 0xa6, + I32_Wrap_I64 = 0xa7, + I32_Trunc_F32_S = 0xa8, + I32_Trunc_F32_U = 0xa9, + I32_Trunc_F64_S = 0xaa, + I32_Trunc_F64_U = 0xab, + I64_Extend_I32_S = 0xac, + I64_Extend_I32_U = 0xad, + I64_Trunc_F32_S = 0xae, + I64_Trunc_F32_U = 0xaf, + I64_Trunc_F64_S = 0xb0, + I64_Trunc_F64_U = 0xb1, + F32_Convert_I32_S = 0xb2, + F32_Convert_I32_U = 0xb3, + F32_Convert_I64_S = 0xb4, + F32_Convert_I64_U = 0xb5, + F32_Demote_F64 = 0xb6, + F64_Convert_I32_S = 0xb7, + F64_Convert_I32_U = 0xb8, + F64_Convert_I64_S = 0xb9, + F64_Convert_I64_U = 0xba, + F64_Promote_F32 = 0xbb, + I32_Reinterpret_F32 = 0xbc, + I64_Reinterpret_F64 = 0xbd, + F32_Reinterpret_I32 = 0xbe, + F64_Reinterpret_I64 = 0xbf, + I32_Extend8_S = 0xc0, + I32_Extend16_S = 0xc1, + I64_Extend8_S = 0xc2, + I64_Extend16_S = 0xc3, + I64_Extend32_S = 0xc4, + // special + Prefix = 0xfc, + SIMDPrefix = 0xfd, + MTPrefix = 0xfe, + } + + internal enum PrefixOpcode : byte + { + // saturating + I32_Trunc_Sat_F32_S = 0, + I32_Trunc_Sat_F32_U = 1, + I32_Trunc_Sat_F64_S = 2, + I32_Trunc_Sat_F64_U = 3, + I64_Trunc_Sat_F32_S = 4, + I64_Trunc_Sat_F32_U = 5, + I64_Trunc_Sat_F64_S = 6, + I64_Trunc_Sat_F64_U = 7, + // memory + Memory_Init = 8, + Data_Drop = 9, + Memory_Copy = 10, + Memory_Fill = 11, + // table + Table_Init = 12, + Elem_Drop = 13, + Table_Copy = 14, + Table_Grow = 15, + Table_Size = 16, + Table_Fill = 17, + } +} + +#endregion From 0d39804ff0109c824cc498eb184f27c47c4b0ef7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Fi=C5=A1era?= Date: Wed, 7 Dec 2022 11:32:22 +0100 Subject: [PATCH 27/33] ICU+timezones --- src/mono/wasm/test-main.js | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/mono/wasm/test-main.js b/src/mono/wasm/test-main.js index 967696bb32fa0e..dcbf8750eca00e 100644 --- a/src/mono/wasm/test-main.js +++ b/src/mono/wasm/test-main.js @@ -283,6 +283,16 @@ async function run() { assets: [{ behavior: "dotnetwasm", name: "dotnet.wasm" + }, + { + loadRemote: false, + behavior: "icu", + name: "icudt.dat" + }, + { + virtualPath: "/usr/share/zoneinfo/", + behavior: "vfs", + name: "dotnet.timezones.blat" }], memory: true }) From f2508f9b505da8858d79dd11b9fc60f0f548cac4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Fi=C5=A1era?= Date: Wed, 7 Dec 2022 11:46:45 +0100 Subject: [PATCH 28/33] More debug message for creating snapshot. --- src/mono/wasm/build/WasmApp.Native.targets | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/mono/wasm/build/WasmApp.Native.targets b/src/mono/wasm/build/WasmApp.Native.targets index 0e68a6e78b5f3e..c1bd7fb14e8379 100644 --- a/src/mono/wasm/build/WasmApp.Native.targets +++ b/src/mono/wasm/build/WasmApp.Native.targets @@ -674,10 +674,12 @@ + + - + From ac5a7e4ca3928651d90cc788e77c03d3bdd9da6f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Fi=C5=A1era?= Date: Wed, 7 Dec 2022 14:27:15 +0100 Subject: [PATCH 29/33] Fix need for ICU --- src/mono/wasm/runtime/icu.ts | 2 +- src/mono/wasm/test-main.js | 10 ---------- 2 files changed, 1 insertion(+), 11 deletions(-) diff --git a/src/mono/wasm/runtime/icu.ts b/src/mono/wasm/runtime/icu.ts index 3e6e7105335c48..825605b68194e6 100644 --- a/src/mono/wasm/runtime/icu.ts +++ b/src/mono/wasm/runtime/icu.ts @@ -37,7 +37,7 @@ export function mono_wasm_globalization_init(): void { invariantMode = true; if (!invariantMode) { - if (num_icu_assets_loaded_successfully > 0) { + if (num_icu_assets_loaded_successfully > 0 || config.memory) { if (runtimeHelpers.diagnosticTracing) { console.debug("MONO_WASM: ICU data archive(s) loaded, disabling invariant mode"); } diff --git a/src/mono/wasm/test-main.js b/src/mono/wasm/test-main.js index dcbf8750eca00e..967696bb32fa0e 100644 --- a/src/mono/wasm/test-main.js +++ b/src/mono/wasm/test-main.js @@ -283,16 +283,6 @@ async function run() { assets: [{ behavior: "dotnetwasm", name: "dotnet.wasm" - }, - { - loadRemote: false, - behavior: "icu", - name: "icudt.dat" - }, - { - virtualPath: "/usr/share/zoneinfo/", - behavior: "vfs", - name: "dotnet.timezones.blat" }], memory: true }) From eb12295daa69a27da06fb668634db5021b3a0f04 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Fi=C5=A1era?= Date: Wed, 7 Dec 2022 14:41:19 +0100 Subject: [PATCH 30/33] Initial heap ~64MB and print snapshot size --- src/mono/wasm/build/WasmApp.Native.targets | 2 +- src/mono/wasm/wasm.proj | 2 +- src/tasks/WasmAppBuilder/AppendMemorySnapshotToWasmFile.cs | 2 ++ 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/mono/wasm/build/WasmApp.Native.targets b/src/mono/wasm/build/WasmApp.Native.targets index c1bd7fb14e8379..125e7d849c9fa1 100644 --- a/src/mono/wasm/build/WasmApp.Native.targets +++ b/src/mono/wasm/build/WasmApp.Native.targets @@ -191,7 +191,7 @@ <_EmccLinkRsp>$(_WasmIntermediateOutputPath)emcc-link.rsp $(EmccTotalMemory) - 536870912 + 67108864 diff --git a/src/mono/wasm/wasm.proj b/src/mono/wasm/wasm.proj index b049dcfbabb055..71fbb422e65caf 100644 --- a/src/mono/wasm/wasm.proj +++ b/src/mono/wasm/wasm.proj @@ -158,7 +158,7 @@ <_EmccExportedRuntimeMethods>"[@(EmccExportedRuntimeMethod -> '%27%(Identity)%27', ',')]" <_EmccExportedFunctions>@(EmccExportedFunction -> '%(Identity)',',') - 536870912 + 67108864 <_EmccLinkFlags Include="-s INITIAL_MEMORY=$(EmccInitialHeapSize)" /> diff --git a/src/tasks/WasmAppBuilder/AppendMemorySnapshotToWasmFile.cs b/src/tasks/WasmAppBuilder/AppendMemorySnapshotToWasmFile.cs index a0ba00c1c3708d..4032cf7bd5da0c 100644 --- a/src/tasks/WasmAppBuilder/AppendMemorySnapshotToWasmFile.cs +++ b/src/tasks/WasmAppBuilder/AppendMemorySnapshotToWasmFile.cs @@ -25,6 +25,8 @@ public class AppendMemorySnapshotToWasmFile : Task public override bool Execute() { + Log.LogMessage(MessageImportance.High, $"Memory snapshot at '{DataSectionFile}' size '{new FileInfo(DataSectionFile).Length}'"); + using var reader = new WasmRewriter(Source, Destination, DataSectionFile, 0, DataMode.Active, Verbose); reader.Parse(); From 15949d0ab67ddcd0c9f22adff663b09c1f7d9a30 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Fi=C5=A1era?= Date: Thu, 8 Dec 2022 11:56:50 +0100 Subject: [PATCH 31/33] Update browser-bench. --- src/mono/sample/wasm/browser-bench/frame-main.js | 9 +++++++++ src/mono/sample/wasm/browser-bench/main.js | 11 +++++++++++ src/mono/wasm/runtime/dotnet.d.ts | 2 +- src/mono/wasm/runtime/startup.ts | 9 +++++++++ 4 files changed, 30 insertions(+), 1 deletion(-) diff --git a/src/mono/sample/wasm/browser-bench/frame-main.js b/src/mono/sample/wasm/browser-bench/frame-main.js index 667f5eec947d5e..31a7e4f26dd15a 100644 --- a/src/mono/sample/wasm/browser-bench/frame-main.js +++ b/src/mono/sample/wasm/browser-bench/frame-main.js @@ -31,7 +31,16 @@ try { } const runtime = await dotnet + .withConfig({ + mainAssemblyName: "Wasm.Browser.Bench.Sample.dll", + assets: [{ + behavior: "dotnetwasm", + name: "dotnet.wasm" + }], + memory: true + }) .withModuleConfig({ + configSrc: null, printErr: () => undefined, print: () => undefined, onConfigLoaded: (config) => { diff --git a/src/mono/sample/wasm/browser-bench/main.js b/src/mono/sample/wasm/browser-bench/main.js index 403fd89b78eb55..1f8a3b77d60662 100644 --- a/src/mono/sample/wasm/browser-bench/main.js +++ b/src/mono/sample/wasm/browser-bench/main.js @@ -183,6 +183,17 @@ try { .withRuntimeOptions(["--jiterpreter-stats-enabled"]) .withElementOnExit() .withExitCodeLogging() + .withConfig({ + mainAssemblyName: "Wasm.Browser.Bench.Sample.dll", + assets: [{ + behavior: "dotnetwasm", + name: "dotnet.wasm" + }], + memory: true + }) + .withModuleConfig({ + configSrc: null, + }) .create(); await mainApp.init(runtime); diff --git a/src/mono/wasm/runtime/dotnet.d.ts b/src/mono/wasm/runtime/dotnet.d.ts index b5110e2fcd3d43..4b1f00d39b1ec4 100644 --- a/src/mono/wasm/runtime/dotnet.d.ts +++ b/src/mono/wasm/runtime/dotnet.d.ts @@ -128,7 +128,7 @@ type MonoConfig = { * initial number of workers to add to the emscripten pthread pool */ pthreadPoolSize?: number; - memory?: Int8Array; + memory?: Int8Array | boolean; }; interface ResourceRequest { name: string; diff --git a/src/mono/wasm/runtime/startup.ts b/src/mono/wasm/runtime/startup.ts index 74e5fda6bb24fe..b7e528b48507e1 100644 --- a/src/mono/wasm/runtime/startup.ts +++ b/src/mono/wasm/runtime/startup.ts @@ -560,6 +560,15 @@ export async function mono_wasm_load_config(configFilePath?: string): PromiseruntimeHelpers.config); + } + catch (err: any) { + _print_error("MONO_WASM: onConfigLoaded() failed", err); + throw err; + } + } normalize(); afterConfigLoaded.promise_control.resolve(runtimeHelpers.config); return; From fc087c83da7ced1e67f9e23264fbd51c8444d1c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Fi=C5=A1era?= Date: Thu, 15 Dec 2022 11:59:02 +0100 Subject: [PATCH 32/33] Optional path to node. --- src/mono/wasm/build/WasmApp.Native.targets | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/mono/wasm/build/WasmApp.Native.targets b/src/mono/wasm/build/WasmApp.Native.targets index 125e7d849c9fa1..2e70e9175da6c6 100644 --- a/src/mono/wasm/build/WasmApp.Native.targets +++ b/src/mono/wasm/build/WasmApp.Native.targets @@ -667,6 +667,7 @@ wasm-memory-snapshot-capture.js memory.dat + $(EmscriptenNodeExecutable) @@ -675,7 +676,7 @@ - + From 088a3463cc385d0a14df1a808f003bacaaa16c8b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Fi=C5=A1era?= Date: Mon, 9 Jan 2023 13:58:38 +0100 Subject: [PATCH 33/33] Zero out unmanaged memory. --- src/mono/wasm/build/WasmApp.Native.targets | 4 +++- src/mono/wasm/runtime/dotnet.d.ts | 2 ++ src/mono/wasm/runtime/run-outer.ts | 20 +++++++++++++++++++- src/mono/wasm/runtime/types.ts | 2 ++ 4 files changed, 26 insertions(+), 2 deletions(-) diff --git a/src/mono/wasm/build/WasmApp.Native.targets b/src/mono/wasm/build/WasmApp.Native.targets index 2e70e9175da6c6..734e51ae7b7a45 100644 --- a/src/mono/wasm/build/WasmApp.Native.targets +++ b/src/mono/wasm/build/WasmApp.Native.targets @@ -200,6 +200,7 @@ <_EmccCommonFlags Include="$(_DefaultEmccFlags)" /> <_EmccCommonFlags Include="$(EmccFlags)" /> <_EmccCommonFlags Include="-s EXPORT_ES6=1" /> + <_EmccCommonFlags Include="--tracing" Condition="'$(WasmMemorySnapshotClearUnmanaged)' != 'false'" /> <_EmccCommonFlags Include="-g" Condition="'$(WasmNativeStrip)' == 'false'" /> <_EmccCommonFlags Include="-v" Condition="'$(EmccVerbose)' != 'false'" /> <_EmccCommonFlags Include="-s DISABLE_EXCEPTION_CATCHING=0" Condition="'$(WasmEnableExceptionHandling)' == 'false'" /> @@ -671,8 +672,9 @@ - + + diff --git a/src/mono/wasm/runtime/dotnet.d.ts b/src/mono/wasm/runtime/dotnet.d.ts index 4b1f00d39b1ec4..bc8faf054debd0 100644 --- a/src/mono/wasm/runtime/dotnet.d.ts +++ b/src/mono/wasm/runtime/dotnet.d.ts @@ -179,6 +179,8 @@ type DotnetModuleConfig = { configSrc?: string; onConfigLoaded?: (config: MonoConfig) => void | Promise; onDotnetReady?: () => void | Promise; + onMalloc?: (pointer: number, length: number) => void; + onFree?: (pointer: number) => void; imports?: any; exports?: string[]; downloadResource?: (request: ResourceRequest) => LoadingResource | undefined; diff --git a/src/mono/wasm/runtime/run-outer.ts b/src/mono/wasm/runtime/run-outer.ts index 6c4115a292eef9..4736932f3de389 100644 --- a/src/mono/wasm/runtime/run-outer.ts +++ b/src/mono/wasm/runtime/run-outer.ts @@ -2,7 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. // WARNING: code in this file is executed before any of the emscripten code, so there is very little initialized already -import { emscriptenEntrypoint, runtimeHelpers } from "./imports"; +import { emscriptenEntrypoint, Module, runtimeHelpers } from "./imports"; import { setup_proxy_console } from "./logging"; import { mono_exit } from "./run"; import { DotnetModuleConfig, MonoConfig, MonoConfigInternal, mono_assert, RuntimeAPI } from "./types"; @@ -30,6 +30,7 @@ class HostBuilder implements DotnetHostBuilder { private instance?: RuntimeAPI; private applicationArguments?: string[]; private virtualWorkingDirectory?: string; + private totalFreed = 0; private moduleConfig: DotnetModuleConfig = { disableDotnet6Compatibility: true, configSrc: "./mono-config.json", @@ -251,6 +252,22 @@ class HostBuilder implements DotnetHostBuilder { } } + withMemoryCleanup(): DotnetHostBuilder { + const pointers:any[] = []; + return this.withModuleConfig({ + onMalloc: (pointer:number, length:number) => pointers[pointer] = length, + onFree: (pointer) => { + const length = pointers[pointer]; + if (length) { + this.totalFreed += length; + for (let p = 0; p < length; p++) { + Module.HEAP8.set([0], pointer + p); + } + } + } + }); + } + withApplicationArgumentsFromQuery(): DotnetHostBuilder { try { if (!globalThis.window) { @@ -287,6 +304,7 @@ class HostBuilder implements DotnetHostBuilder { mono_assert(this.moduleConfig, "Null moduleConfig"); mono_assert(this.moduleConfig.config, "Null moduleConfig.config"); this.instance = await emscriptenEntrypoint(this.moduleConfig); + (this.instance as any).totalFreed = this.totalFreed; } if (this.virtualWorkingDirectory) { const FS = (this.instance!.Module as any).FS; diff --git a/src/mono/wasm/runtime/types.ts b/src/mono/wasm/runtime/types.ts index 3d42cec0cd0119..927cd4246bfa92 100644 --- a/src/mono/wasm/runtime/types.ts +++ b/src/mono/wasm/runtime/types.ts @@ -252,6 +252,8 @@ export type DotnetModuleConfig = { configSrc?: string, onConfigLoaded?: (config: MonoConfig) => void | Promise; onDotnetReady?: () => void | Promise; + onMalloc?: (pointer: number, length: number) => void; + onFree?: (pointer: number) => void; imports?: any; exports?: string[];