From 9e8eb7eb133e6726fd3c9f0151c5636c6b348e6a Mon Sep 17 00:00:00 2001 From: Pavel Savara Date: Sat, 25 Sep 2021 13:37:56 +0200 Subject: [PATCH] - refactor wasm javascript as typescript - use nodeJs from EMSDK - use rollup.js for packing code as ES6 IFFE for emcc consumption - incremental build Co-authored-by: Radek Doulik - types Co-authored-by: Daniel Genkin --- .../Directory.Build.props | 1 + src/mono/wasm/Makefile | 2 +- src/mono/wasm/build/WasmApp.Native.targets | 5 +- src/mono/wasm/runtime-test.js | 1 + src/mono/wasm/runtime/CMakeLists.txt | 4 +- src/mono/wasm/runtime/library_mono.js | 1556 +---------------- src/mono/wasm/runtime/package-lock.json | 328 ++++ src/mono/wasm/runtime/package.json | 26 + src/mono/wasm/runtime/rollup.config.js | 69 + src/mono/wasm/runtime/src/binding/types.ts | 14 + src/mono/wasm/runtime/src/mono/base64.ts | 121 ++ src/mono/wasm/runtime/src/mono/cwraps.ts | 57 + src/mono/wasm/runtime/src/mono/debug.ts | 291 +++ src/mono/wasm/runtime/src/mono/icu.ts | 60 + src/mono/wasm/runtime/src/mono/init.ts | 490 ++++++ src/mono/wasm/runtime/src/mono/profiler.ts | 48 + src/mono/wasm/runtime/src/mono/roots.ts | 304 ++++ src/mono/wasm/runtime/src/mono/scheduling.ts | 78 + .../wasm/runtime/src/mono/string-decoder.ts | 81 + src/mono/wasm/runtime/src/mono/types.ts | 157 ++ src/mono/wasm/runtime/src/runtime.ts | 14 + src/mono/wasm/runtime/src/startup.ts | 70 + .../wasm/runtime/src/types/emscripten.d.ts | 56 + src/mono/wasm/runtime/src/types/v8.d.ts | 5 + src/mono/wasm/runtime/tsconfig.json | 20 + src/mono/wasm/wasm.proj | 13 +- 26 files changed, 2319 insertions(+), 1552 deletions(-) create mode 100644 src/mono/wasm/runtime/package-lock.json create mode 100644 src/mono/wasm/runtime/package.json create mode 100644 src/mono/wasm/runtime/rollup.config.js create mode 100644 src/mono/wasm/runtime/src/binding/types.ts create mode 100644 src/mono/wasm/runtime/src/mono/base64.ts create mode 100644 src/mono/wasm/runtime/src/mono/cwraps.ts create mode 100644 src/mono/wasm/runtime/src/mono/debug.ts create mode 100644 src/mono/wasm/runtime/src/mono/icu.ts create mode 100644 src/mono/wasm/runtime/src/mono/init.ts create mode 100644 src/mono/wasm/runtime/src/mono/profiler.ts create mode 100644 src/mono/wasm/runtime/src/mono/roots.ts create mode 100644 src/mono/wasm/runtime/src/mono/scheduling.ts create mode 100644 src/mono/wasm/runtime/src/mono/string-decoder.ts create mode 100644 src/mono/wasm/runtime/src/mono/types.ts create mode 100644 src/mono/wasm/runtime/src/runtime.ts create mode 100644 src/mono/wasm/runtime/src/startup.ts create mode 100644 src/mono/wasm/runtime/src/types/emscripten.d.ts create mode 100644 src/mono/wasm/runtime/src/types/v8.d.ts create mode 100644 src/mono/wasm/runtime/tsconfig.json 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 15d09343b0503..f6cdff8b24e98 100644 --- a/src/installer/pkg/sfx/Microsoft.NETCore.App/Directory.Build.props +++ b/src/installer/pkg/sfx/Microsoft.NETCore.App/Directory.Build.props @@ -217,6 +217,7 @@ + diff --git a/src/mono/wasm/Makefile b/src/mono/wasm/Makefile index 7d48a7e6f98aa..c4aebc3327c48 100644 --- a/src/mono/wasm/Makefile +++ b/src/mono/wasm/Makefile @@ -101,7 +101,7 @@ $(NATIVE_BIN_DIR)/include/wasm: $(BUILDS_OBJ_DIR): mkdir -p $$@ -$(NATIVE_BIN_DIR)/dotnet.js: runtime/library_mono.js runtime/binding_support.js runtime/dotnet_support.js $(SYSTEM_NATIVE_LIBDIR)/pal_random.js $(2) $(EMCC_DEFAULT_RSP) | $(NATIVE_BIN_DIR) +$(NATIVE_BIN_DIR)/dotnet.js: $(NATIVE_BIN_DIR)/src/runtime.iffe.js runtime/library_mono.js runtime/binding_support.js runtime/dotnet_support.js $(SYSTEM_NATIVE_LIBDIR)/pal_random.js $(2) $(EMCC_DEFAULT_RSP) | $(NATIVE_BIN_DIR) $(DOTNET) build $(CURDIR)/wasm.proj _MSBUILD_WASM_BUILD_ARGS /t:BuildWasmRuntimes $(EMCC_DEFAULT_RSP): $(CURDIR)/wasm.proj | $(NATIVE_BIN_DIR)/src Makefile diff --git a/src/mono/wasm/build/WasmApp.Native.targets b/src/mono/wasm/build/WasmApp.Native.targets index 19737849bdaee..5a08fee4e2839 100644 --- a/src/mono/wasm/build/WasmApp.Native.targets +++ b/src/mono/wasm/build/WasmApp.Native.targets @@ -217,7 +217,8 @@ <_WasmRuntimePackSrcFile ObjectFile="$(_WasmIntermediateOutputPath)%(FileName).o" /> - <_DotnetJSSrcFile Include="$(_WasmRuntimePackSrcDir)\*.js" /> + <_DotnetJSSrcFile Include="$(_WasmRuntimePackSrcDir)\*.js" Exclude="$(_WasmRuntimePackSrcDir)\*.iffe.js"/> + <_DotnetJSPreFile Include="$(_WasmRuntimePackSrcDir)\*.iffe.js"/> <_WasmNativeFileForLinking Include="@(NativeFileReference)" /> @@ -337,7 +338,9 @@ <_EmccLinkStepArgs Include="@(_EmccLDFlags)" /> <_EmccLinkStepArgs Include="--js-library "%(_DotnetJSSrcFile.Identity)"" /> + <_EmccLinkStepArgs Include="--pre-js "%(_DotnetJSPreFile.Identity)"" /> <_WasmLinkDependencies Include="@(_DotnetJSSrcFile)" /> + <_WasmLinkDependencies Include="@(_DotnetJSPreFile)" /> <_EmccLinkStepArgs Include="--js-library "%(_WasmExtraJSFile.Identity)"" Condition="'%(_WasmExtraJSFile.Kind)' == 'js-library'" /> <_EmccLinkStepArgs Include="--pre-js "%(_WasmExtraJSFile.Identity)"" Condition="'%(_WasmExtraJSFile.Kind)' == 'pre-js'" /> diff --git a/src/mono/wasm/runtime-test.js b/src/mono/wasm/runtime-test.js index 76c02dfe63f85..8d8b89bd0bd5a 100644 --- a/src/mono/wasm/runtime-test.js +++ b/src/mono/wasm/runtime-test.js @@ -274,6 +274,7 @@ var Module = { try { bytes = read (asset, 'binary'); } catch (exc) { + console.log('v8 file read failed ' + asset + ' ' + exc) error = exc; } var response = { ok: (bytes && !error), url: asset, diff --git a/src/mono/wasm/runtime/CMakeLists.txt b/src/mono/wasm/runtime/CMakeLists.txt index ee2cf5f608308..8e20ff644a19d 100644 --- a/src/mono/wasm/runtime/CMakeLists.txt +++ b/src/mono/wasm/runtime/CMakeLists.txt @@ -21,8 +21,8 @@ target_link_libraries(dotnet set_target_properties(dotnet PROPERTIES CMAKE_EXECUTABLE_SUFFIX ".js" - LINK_DEPENDS "${NATIVE_BIN_DIR}/src/emcc-default.rsp;${SOURCE_DIR}/library_mono.js;${SOURCE_DIR}/binding_support.js;${SOURCE_DIR}/dotnet_support.js;${SYSTEM_NATIVE_DIR}/pal_random.js" - LINK_FLAGS "@${NATIVE_BIN_DIR}/src/emcc-default.rsp ${CONFIGURATION_EMCC_FLAGS} -DENABLE_NETCORE=1 --js-library ${SOURCE_DIR}/library_mono.js --js-library ${SOURCE_DIR}/binding_support.js --js-library ${SOURCE_DIR}/dotnet_support.js --js-library ${SYSTEM_NATIVE_DIR}/pal_random.js" + LINK_DEPENDS "${NATIVE_BIN_DIR}/src/emcc-default.rsp;${NATIVE_BIN_DIR}/src/runtime.iffe.js;${SOURCE_DIR}/library_mono.js;${SOURCE_DIR}/binding_support.js;${SOURCE_DIR}/dotnet_support.js;${SYSTEM_NATIVE_DIR}/pal_random.js" + LINK_FLAGS "@${NATIVE_BIN_DIR}/src/emcc-default.rsp ${CONFIGURATION_EMCC_FLAGS} -DENABLE_NETCORE=1 --pre-js ${NATIVE_BIN_DIR}/src/runtime.iffe.js --js-library ${SOURCE_DIR}/library_mono.js --js-library ${SOURCE_DIR}/binding_support.js --js-library ${SOURCE_DIR}/dotnet_support.js --js-library ${SYSTEM_NATIVE_DIR}/pal_random.js" RUNTIME_OUTPUT_DIRECTORY "${NATIVE_BIN_DIR}") add_custom_command(TARGET dotnet POST_BUILD COMMAND ${EMSDK_PATH}/upstream/bin/wasm-opt --strip-dwarf ${NATIVE_BIN_DIR}/dotnet.wasm -o ${NATIVE_BIN_DIR}/dotnet.wasm) diff --git a/src/mono/wasm/runtime/library_mono.js b/src/mono/wasm/runtime/library_mono.js index 2f9fe8a5e19c5..462b042fbe711 100644 --- a/src/mono/wasm/runtime/library_mono.js +++ b/src/mono/wasm/runtime/library_mono.js @@ -3,1552 +3,16 @@ "use strict"; -/** - * @typedef WasmId - * @type {object} - * @property {string} idStr - full object id string - * @property {string} scheme - eg, object, valuetype, array .. - * @property {string} value - string part after `dotnet:scheme:` of the id string - * @property {object} o - value parsed as JSON - */ - -/** - * @typedef WasmRoot - a single address in the managed heap, visible to the GC - * @type {object} - * @property {ManagedPointer} value - pointer into the managed heap, stored in the root - * @property {function} get_address - retrieves address of the root in wasm memory - * @property {function} get - retrieves pointer value - * @property {function} set - updates the pointer - * @property {function} release - releases the root storage for future use - */ - -/** - * @typedef WasmRootBuffer - a collection of addresses in the managed heap, visible to the GC - * @type {object} - * @property {number} length - number of elements the root buffer can hold - * @property {function} get_address - retrieves address of an element in wasm memory, by index - * @property {function} get - retrieves an element by index - * @property {function} set - sets an element's value by index - * @property {function} release - releases the root storage for future use - */ - -/** - * @typedef ManagedPointer - * @type {number} - address in the managed heap - */ - -/** - * @typedef NativePointer - * @type {number} - address in wasm memory - */ - -/** - * @typedef Event - * @type {object} - * @property {string} eventName - name of the event being raised - * @property {object} eventArgs - arguments for the event itself - */ - -var MonoSupportLib = { - $MONO__postset: 'MONO.export_functions (Module);', - $MONO: { - active_frames: [], - pump_count: 0, - timeout_queue: [], - spread_timers_maximum: 0, - isChromium: false, - _vt_stack: [], - mono_wasm_runtime_is_ready : false, - mono_wasm_ignore_pdb_load_errors: true, - num_icu_assets_loaded_successfully: 0, - _async_method_objectId: 0, - _next_id_var: 0, - _next_call_function_res_id: 0, - _scratch_root_buffer: null, - _scratch_root_free_indices: null, - _scratch_root_free_indices_count: 0, - _scratch_root_free_instances: [], - _vt_stack: [], - - /** @type {object.} */ - _id_table: {}, - - pump_message: function () { - if (!MONO.mono_background_exec) - MONO.mono_background_exec = Module.cwrap ("mono_background_exec", null); - while (MONO.timeout_queue.length > 0) { - --MONO.pump_count; - MONO.timeout_queue.shift()(); - } - while (MONO.pump_count > 0) { - --MONO.pump_count; - MONO.mono_background_exec (); - } - }, - - export_functions: function (module) { - module ["pump_message"] = MONO.pump_message.bind(MONO); - module ["prevent_timer_throttling"] = MONO.prevent_timer_throttling.bind(MONO); - module ["mono_wasm_set_timeout_exec"] = MONO.mono_wasm_set_timeout_exec.bind(MONO); - module ["mono_load_runtime_and_bcl"] = MONO.mono_load_runtime_and_bcl.bind(MONO); - module ["mono_load_runtime_and_bcl_args"] = MONO.mono_load_runtime_and_bcl_args.bind(MONO); - module ["mono_wasm_load_bytes_into_heap"] = MONO.mono_wasm_load_bytes_into_heap.bind(MONO); - module ["mono_wasm_load_icu_data"] = MONO.mono_wasm_load_icu_data.bind(MONO); - module ["mono_wasm_get_icudt_name"] = MONO.mono_wasm_get_icudt_name.bind(MONO); - module ["mono_wasm_globalization_init"] = MONO.mono_wasm_globalization_init.bind(MONO); - module ["mono_wasm_get_loaded_files"] = MONO.mono_wasm_get_loaded_files.bind(MONO); - module ["mono_wasm_new_root_buffer"] = MONO.mono_wasm_new_root_buffer.bind(MONO); - module ["mono_wasm_new_root_buffer_from_pointer"] = MONO.mono_wasm_new_root_buffer_from_pointer.bind(MONO); - module ["mono_wasm_new_root"] = MONO.mono_wasm_new_root.bind(MONO); - module ["mono_wasm_new_roots"] = MONO.mono_wasm_new_roots.bind(MONO); - module ["mono_wasm_release_roots"] = MONO.mono_wasm_release_roots.bind(MONO); - module ["mono_wasm_load_config"] = MONO.mono_wasm_load_config.bind(MONO); - - if (globalThis.navigator) { - const nav = globalThis.navigator; - if (nav.userAgentData && nav.userAgentData.brands) { - MONO.isChromium = nav.userAgentData.brands.some((i) => i.brand == 'Chromium'); - } - else if (nav.userAgent) { - MONO.isChromium = nav.userAgent.includes("Chrome"); - } - } - }, - - _base64Converter: { - // Code from JSIL: - // https://github.com/sq/JSIL/blob/1d57d5427c87ab92ffa3ca4b82429cd7509796ba/JSIL.Libraries/Includes/Bootstrap/Core/Classes/System.Convert.js#L149 - // Thanks to Katelyn Gadd @kg - - _base64Table: [ - 'A', 'B', 'C', 'D', - 'E', 'F', 'G', 'H', - 'I', 'J', 'K', 'L', - 'M', 'N', 'O', 'P', - 'Q', 'R', 'S', 'T', - 'U', 'V', 'W', 'X', - 'Y', 'Z', - 'a', 'b', 'c', 'd', - 'e', 'f', 'g', 'h', - 'i', 'j', 'k', 'l', - 'm', 'n', 'o', 'p', - 'q', 'r', 's', 't', - 'u', 'v', 'w', 'x', - 'y', 'z', - '0', '1', '2', '3', - '4', '5', '6', '7', - '8', '9', - '+', '/' - ], - - _makeByteReader: function (bytes, index, count) { - var position = (typeof (index) === "number") ? index : 0; - var endpoint; - - if (typeof (count) === "number") - endpoint = (position + count); - else - endpoint = (bytes.length - position); - - var result = { - read: function () { - if (position >= endpoint) - return false; - - var nextByte = bytes[position]; - position += 1; - return nextByte; - } - }; - - Object.defineProperty(result, "eof", { - get: function () { - return (position >= endpoint); - }, - configurable: true, - enumerable: true - }); - - return result; - }, - - toBase64StringImpl: function (inArray, offset, length) { - var reader = this._makeByteReader(inArray, offset, length); - var result = ""; - var ch1 = 0, ch2 = 0, ch3 = 0, bits = 0, equalsCount = 0, sum = 0; - var mask1 = (1 << 24) - 1, mask2 = (1 << 18) - 1, mask3 = (1 << 12) - 1, mask4 = (1 << 6) - 1; - var shift1 = 18, shift2 = 12, shift3 = 6, shift4 = 0; - - while (true) { - ch1 = reader.read(); - ch2 = reader.read(); - ch3 = reader.read(); - - if (ch1 === false) - break; - if (ch2 === false) { - ch2 = 0; - equalsCount += 1; - } - if (ch3 === false) { - ch3 = 0; - equalsCount += 1; - } - - // Seems backwards, but is right! - sum = (ch1 << 16) | (ch2 << 8) | (ch3 << 0); - - bits = (sum & mask1) >> shift1; - result += this._base64Table[bits]; - bits = (sum & mask2) >> shift2; - result += this._base64Table[bits]; - - if (equalsCount < 2) { - bits = (sum & mask3) >> shift3; - result += this._base64Table[bits]; - } - - if (equalsCount === 2) { - result += "=="; - } else if (equalsCount === 1) { - result += "="; - } else { - bits = (sum & mask4) >> shift4; - result += this._base64Table[bits]; - } - } - - return result; - }, - }, - - _mono_wasm_root_buffer_prototype: { - _throw_index_out_of_range: function () { - throw new Error ("index out of range"); - }, - _check_in_range: function (index) { - if ((index >= this.__count) || (index < 0)) - this._throw_index_out_of_range(); - }, - /** @returns {NativePointer} */ - get_address: function (index) { - this._check_in_range (index); - return this.__offset + (index * 4); - }, - /** @returns {number} */ - get_address_32: function (index) { - this._check_in_range (index); - return this.__offset32 + index; - }, - /** @returns {ManagedPointer} */ - get: function (index) { - this._check_in_range (index); - return Module.HEAP32[this.get_address_32 (index)]; - }, - set: function (index, value) { - Module.HEAP32[this.get_address_32 (index)] = value; - return value; - }, - _unsafe_get: function (index) { - return Module.HEAP32[this.__offset32 + index]; - }, - _unsafe_set: function (index, value) { - Module.HEAP32[this.__offset32 + index] = value; - }, - clear: function () { - if (this.__offset) - MONO._zero_region (this.__offset, this.__count * 4); - }, - release: function () { - if (this.__offset && this.__ownsAllocation) { - MONO.mono_wasm_deregister_root (this.__offset); - MONO._zero_region (this.__offset, this.__count * 4); - Module._free (this.__offset); - } - - this.__handle = this.__offset = this.__count = this.__offset32 = 0; - }, - toString: function () { - return "[root buffer @" + this.get_address (0) + ", size " + this.__count + "]"; - } - }, - - _mono_wasm_root_prototype: { - /** @returns {NativePointer} */ - get_address: function () { - return this.__buffer.get_address (this.__index); - }, - /** @returns {number} */ - get_address_32: function () { - return this.__buffer.get_address_32 (this.__index); - }, - /** @returns {ManagedPointer} */ - get: function () { - var result = this.__buffer._unsafe_get (this.__index); - return result; - }, - set: function (value) { - this.__buffer._unsafe_set (this.__index, value); - return value; - }, - /** @returns {ManagedPointer} */ - valueOf: function () { - return this.get (); - }, - clear: function () { - this.set (0); - }, - release: function () { - const maxPooledInstances = 128; - if (MONO._scratch_root_free_instances.length > maxPooledInstances) { - MONO._mono_wasm_release_scratch_index (this.__index); - this.__buffer = 0; - this.__index = 0; - } else { - this.set (0); - MONO._scratch_root_free_instances.push (this); - } - }, - toString: function () { - return "[root @" + this.get_address () + "]"; - } - }, - - _mono_wasm_release_scratch_index: function (index) { - if (index === undefined) - return; - - MONO._scratch_root_buffer.set (index, 0); - MONO._scratch_root_free_indices[MONO._scratch_root_free_indices_count] = index; - MONO._scratch_root_free_indices_count++; - }, - - _mono_wasm_claim_scratch_index: function () { - if (!MONO._scratch_root_buffer) { - const maxScratchRoots = 8192; - MONO._scratch_root_buffer = this.mono_wasm_new_root_buffer (maxScratchRoots, "js roots"); - - MONO._scratch_root_free_indices = new Int32Array (maxScratchRoots); - MONO._scratch_root_free_indices_count = maxScratchRoots; - for (var i = 0; i < maxScratchRoots; i++) - MONO._scratch_root_free_indices[i] = maxScratchRoots - i - 1; - - Object.defineProperty (this._mono_wasm_root_prototype, "value", { - get: this._mono_wasm_root_prototype.get, - set: this._mono_wasm_root_prototype.set, - configurable: false - }); - } - - if (MONO._scratch_root_free_indices_count < 1) - throw new Error ("Out of scratch root space"); - - var result = MONO._scratch_root_free_indices[MONO._scratch_root_free_indices_count - 1]; - MONO._scratch_root_free_indices_count--; - return result; - }, - - _zero_region: function (byteOffset, sizeBytes) { - if (((byteOffset % 4) === 0) && ((sizeBytes % 4) === 0)) - Module.HEAP32.fill(0, byteOffset / 4, sizeBytes / 4); - else - Module.HEAP8.fill(0, byteOffset, sizeBytes); - }, - - /** - * 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. - * @param {number} capacity - the maximum number of elements the buffer can hold. - * @param {string} [msg] - a description of the root buffer (for debugging) - * @returns {WasmRootBuffer} - */ - mono_wasm_new_root_buffer: function (capacity, msg) { - if (!this.mono_wasm_register_root || !this.mono_wasm_deregister_root) { - this.mono_wasm_register_root = Module.cwrap ("mono_wasm_register_root", "number", ["number", "number", "string"]); - this.mono_wasm_deregister_root = Module.cwrap ("mono_wasm_deregister_root", null, ["number"]); - } - - if (capacity <= 0) - throw new Error ("capacity >= 1"); - - capacity = capacity | 0; - - var capacityBytes = capacity * 4; - var offset = Module._malloc (capacityBytes); - if ((offset % 4) !== 0) - throw new Error ("Malloc returned an unaligned offset"); - - this._zero_region (offset, capacityBytes); - - var result = Object.create (this._mono_wasm_root_buffer_prototype); - result.__offset = offset; - result.__offset32 = (offset / 4) | 0; - result.__count = capacity; - result.length = capacity; - result.__handle = this.mono_wasm_register_root (offset, capacityBytes, msg || 0); - result.__ownsAllocation = true; - - return result; - }, - - /** - * Creates a root buffer object representing an existing allocation in the native heap and registers - * the allocation with the GC. The caller is responsible for managing the lifetime of the allocation. - * @param {NativePointer} offset - the offset of the root buffer in the native heap. - * @param {number} capacity - the maximum number of elements the buffer can hold. - * @param {string} [msg] - a description of the root buffer (for debugging) - * @returns {WasmRootBuffer} - */ - mono_wasm_new_root_buffer_from_pointer: function (offset, capacity, msg) { - if (!this.mono_wasm_register_root || !this.mono_wasm_deregister_root) { - this.mono_wasm_register_root = Module.cwrap ("mono_wasm_register_root", "number", ["number", "number", "string"]); - this.mono_wasm_deregister_root = Module.cwrap ("mono_wasm_deregister_root", null, ["number"]); - } - - if (capacity <= 0) - throw new Error ("capacity >= 1"); - - capacity = capacity | 0; - - var capacityBytes = capacity * 4; - if ((offset % 4) !== 0) - throw new Error ("Unaligned offset"); - - this._zero_region (offset, capacityBytes); - - var result = Object.create (this._mono_wasm_root_buffer_prototype); - result.__offset = offset; - result.__offset32 = (offset / 4) | 0; - result.__count = capacity; - result.length = capacity; - result.__handle = this.mono_wasm_register_root (offset, capacityBytes, msg || 0); - result.__ownsAllocation = false; - - return result; - }, - - /** - * 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. - * @param {ManagedPointer} [value] - an address in the managed heap to initialize the root with (or 0) - * @returns {WasmRoot} - */ - mono_wasm_new_root: function (value) { - var result; - - if (MONO._scratch_root_free_instances.length > 0) { - result = MONO._scratch_root_free_instances.pop (); - } else { - var index = this._mono_wasm_claim_scratch_index (); - var buffer = MONO._scratch_root_buffer; - - result = Object.create (this._mono_wasm_root_prototype); - result.__buffer = buffer; - result.__index = index; - } - - if (value !== undefined) { - if (typeof (value) !== "number") - throw new Error ("value must be an address in the managed heap"); - - result.set (value); - } else { - result.set (0); - } - - return result; - }, - - /** - * Allocates 1 or more temporary roots, accepting either a number of roots or an array of pointers. - * mono_wasm_new_roots(n): returns an array of N zero-initialized roots. - * mono_wasm_new_roots([a, b, ...]) returns an array of new roots initialized with each element. - * Each root must be released with its release method, or using the mono_wasm_release_roots API. - * @param {(number | ManagedPointer[])} count_or_values - either a number of roots or an array of pointers - * @returns {WasmRoot[]} - */ - mono_wasm_new_roots: function (count_or_values) { - var result; - - if (Array.isArray (count_or_values)) { - result = new Array (count_or_values.length); - for (var i = 0; i < result.length; i++) - result[i] = this.mono_wasm_new_root (count_or_values[i]); - } else if ((count_or_values | 0) > 0) { - result = new Array (count_or_values); - for (var i = 0; i < result.length; i++) - result[i] = this.mono_wasm_new_root (); - } else { - throw new Error ("count_or_values must be either an array or a number greater than 0"); - } - - return result; - }, - - /** - * 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 - */ - mono_wasm_release_roots: function () { - for (var i = 0; i < arguments.length; i++) { - if (!arguments[i]) - continue; - - arguments[i].release (); - } - }, - - string_decoder: { - copy: function (mono_string) { - if (mono_string === 0) - return null; - - if (!this.mono_wasm_string_root) - this.mono_wasm_string_root = MONO.mono_wasm_new_root (); - this.mono_wasm_string_root.value = mono_string; - - if (!this.mono_wasm_string_get_data) - this.mono_wasm_string_get_data = Module.cwrap ("mono_wasm_string_get_data", null, ['number', 'number', 'number', 'number']); - - if (!this.mono_wasm_string_decoder_buffer) - this.mono_wasm_string_decoder_buffer = Module._malloc(12); - - let ppChars = this.mono_wasm_string_decoder_buffer + 0, - pLengthBytes = this.mono_wasm_string_decoder_buffer + 4, - pIsInterned = this.mono_wasm_string_decoder_buffer + 8; - - this.mono_wasm_string_get_data (mono_string, ppChars, pLengthBytes, pIsInterned); - - // TODO: Is this necessary? - if (!this.mono_wasm_empty_string) - this.mono_wasm_empty_string = ""; - - let result = this.mono_wasm_empty_string; - let lengthBytes = Module.HEAP32[pLengthBytes / 4], - pChars = Module.HEAP32[ppChars / 4], - isInterned = Module.HEAP32[pIsInterned / 4]; - - if (pLengthBytes && pChars) { - if ( - isInterned && - MONO.interned_string_table && - MONO.interned_string_table.has(mono_string) - ) { - result = MONO.interned_string_table.get(mono_string); - // console.log("intern table cache hit", mono_string, result.length); - } else { - result = this.decode(pChars, pChars + lengthBytes, false); - if (isInterned) { - if (!MONO.interned_string_table) - MONO.interned_string_table = new Map(); - // console.log("interned", mono_string, result.length); - MONO.interned_string_table.set(mono_string, result); - } - } - } - - this.mono_wasm_string_root.value = 0; - return result; - }, - decode: function (start, end, save) { - if (MONO.mono_text_decoder === undefined) { - MONO.mono_text_decoder = typeof TextDecoder !== 'undefined' ? new TextDecoder('utf-16le') : null; - } - - var str = ""; - if (MONO.mono_text_decoder) { - // When threading is enabled, TextDecoder does not accept a view of a - // SharedArrayBuffer, we must make a copy of the array first. - // See https://github.com/whatwg/encoding/issues/172 - var subArray = typeof SharedArrayBuffer !== 'undefined' && Module.HEAPU8.buffer instanceof SharedArrayBuffer - ? Module.HEAPU8.slice(start, end) - : Module.HEAPU8.subarray(start, end); - - str = MONO.mono_text_decoder.decode(subArray); - } else { - for (var i = 0; i < end - start; i+=2) { - var char = Module.getValue (start + i, 'i16'); - str += String.fromCharCode (char); - } - } - if (save) - this.result = str; - - return str; - }, - }, - - mono_wasm_add_dbg_command_received: function(res_ok, id, buffer, buffer_len) { - const assembly_data = new Uint8Array(Module.HEAPU8.buffer, buffer, buffer_len); - const base64String = MONO._base64Converter.toBase64StringImpl(assembly_data); - const buffer_obj = { - res_ok, - res: { - id, - value: base64String - } - } - MONO.commands_received = buffer_obj; - }, - - mono_wasm_send_dbg_command_with_parms: function (id, command_set, command, command_parameters, length, valtype, newvalue) - { - var dataHeap = this.mono_wasm_load_bytes_into_heap (this._base64_to_uint8 (command_parameters)); - this._c_fn_table.mono_wasm_send_dbg_command_with_parms_wrapper (id, command_set, command, dataHeap, length, valtype, newvalue.toString()); - let { res_ok, res } = MONO.commands_received; - if (!res_ok) - throw new Error (`Failed on mono_wasm_invoke_method_debugger_agent_with_parms`); - return res; - }, - - mono_wasm_send_dbg_command: function (id, command_set, command, command_parameters) - { - var dataHeap = this.mono_wasm_load_bytes_into_heap (this._base64_to_uint8 (command_parameters)); - this._c_fn_table.mono_wasm_send_dbg_command_wrapper (id, command_set, command, dataHeap, command_parameters.length); - let { res_ok, res } = MONO.commands_received; - if (!res_ok) - throw new Error (`Failed on mono_wasm_send_dbg_command`); - return res; - - }, - - mono_wasm_get_dbg_command_info: function () - { - let { res_ok, res } = MONO.commands_received; - if (!res_ok) - throw new Error (`Failed on mono_wasm_get_dbg_command_info`); - return res; - }, - - _get_cfo_res_details: function (objectId, args) { - if (!(objectId in this._call_function_res_cache)) - throw new Error(`Could not find any object with id ${objectId}`); - - const real_obj = this._call_function_res_cache [objectId]; - - const descriptors = Object.getOwnPropertyDescriptors (real_obj); - if (args.accessorPropertiesOnly) { - Object.keys (descriptors).forEach (k => { - if (descriptors [k].get === undefined) - Reflect.deleteProperty (descriptors, k); - }); - } - - let res_details = []; - Object.keys (descriptors).forEach (k => { - let new_obj; - let prop_desc = descriptors [k]; - if (typeof prop_desc.value == "object") { - // convert `{value: { type='object', ... }}` - // to `{ name: 'foo', value: { type='object', ... }} - new_obj = Object.assign ({ name: k }, prop_desc); - } else if (prop_desc.value !== undefined) { - // This is needed for values that were not added by us, - // thus are like { value: 5 } - // instead of { value: { type = 'number', value: 5 }} - // - // This can happen, for eg., when `length` gets added for arrays - // or `__proto__`. - new_obj = { - name: k, - // merge/add `type` and `description` to `d.value` - value: Object.assign ({ type: (typeof prop_desc.value), description: '' + prop_desc.value }, - prop_desc) - }; - } else if (prop_desc.get !== undefined) { - // The real_obj has the actual getter. We are just returning a placeholder - // If the caller tries to run function on the cfo_res object, - // that accesses this property, then it would be run on `real_obj`, - // which *has* the original getter - new_obj = { - name: k, - get: { - className: "Function", - description: `get ${k} () {}`, - type: "function" - } - }; - } else { - new_obj = { name: k, value: { type: "symbol", value: "", description: ""} }; - } - - res_details.push (new_obj); - }); - - return { __value_as_json_string__: JSON.stringify (res_details) }; - }, - - mono_wasm_get_details: function (objectId, args={}) { - return this._get_cfo_res_details (`dotnet:cfo_res:${objectId}`, args); - }, - - _cache_call_function_res: function (obj) { - const id = `dotnet:cfo_res:${MONO._next_call_function_res_id++}`; - this._call_function_res_cache[id] = obj; - return id; - }, - - mono_wasm_release_object: function (objectId) { - if (objectId in this._cache_call_function_res) - delete this._cache_call_function_res[objectId]; - }, - - _create_proxy_from_object_id: function (objectId, details) { - if (objectId.startsWith ('dotnet:array:')) - { - let ret = details.map (p => p.value); - return ret; - } - - let proxy = {}; - Object.keys (details).forEach (p => { - var prop = details [p]; - if (prop.get !== undefined) { - Object.defineProperty (proxy, - prop.name, - { get () { return MONO.mono_wasm_send_dbg_command(-1, prop.get.commandSet, prop.get.command, prop.get.buffer, prop.get.length); }, - set: function (newValue) { MONO.mono_wasm_send_dbg_command_with_parms(-1, prop.set.commandSet, prop.set.command, prop.set.buffer, prop.set.length, prop.set.valtype, newValue); return MONO.commands_received.res_ok;}} - ); - } else if (prop.set !== undefined ){ - Object.defineProperty (proxy, - prop.name, - { get () { return prop.value; }, - set: function (newValue) { MONO.mono_wasm_send_dbg_command_with_parms(-1, prop.set.commandSet, prop.set.command, prop.set.buffer, prop.set.length, prop.set.valtype, newValue); return MONO.commands_received.res_ok;}} - ); - } else { - proxy [prop.name] = prop.value; - } - }); - return proxy; - }, - - mono_wasm_call_function_on: function (request) { - if (request.arguments != undefined && !Array.isArray (request.arguments)) - throw new Error (`"arguments" should be an array, but was ${request.arguments}`); - - const objId = request.objectId; - const details = request.details; - let proxy; - - if (objId.startsWith ('dotnet:cfo_res:')) { - if (objId in this._call_function_res_cache) - proxy = this._call_function_res_cache [objId]; - else - throw new Error (`Unknown object id ${objId}`); - } else { - proxy = this._create_proxy_from_object_id (objId, details); - } - - const fn_args = request.arguments != undefined ? request.arguments.map(a => JSON.stringify(a.value)) : []; - const fn_eval_str = `var fn = ${request.functionDeclaration}; fn.call (proxy, ...[${fn_args}]);`; - - const fn_res = eval (fn_eval_str); - if (fn_res === undefined) - return { type: "undefined" }; - - if (Object (fn_res) !== fn_res) - { - if (typeof(fn_res) == "object" && fn_res == null) - return { type: typeof(fn_res), subtype: `${fn_res}`, value: null }; - return { type: typeof(fn_res), description: `${fn_res}`, value: `${fn_res}`}; - } - - if (request.returnByValue && fn_res.subtype == undefined) - return {type: "object", value: fn_res}; - if (Object.getPrototypeOf (fn_res) == Array.prototype) { - - const fn_res_id = this._cache_call_function_res (fn_res); - - return { - type: "object", - subtype: "array", - className: "Array", - description: `Array(${fn_res.length})`, - objectId: fn_res_id - }; - } - if (fn_res.value !== undefined || fn_res.subtype !== undefined) { - return fn_res; - } - - if (fn_res == proxy) - return { type: "object", className: "Object", description: "Object", objectId: objId }; - const fn_res_id = this._cache_call_function_res (fn_res); - return { type: "object", className: "Object", description: "Object", objectId: fn_res_id }; - }, - - _clear_per_step_state: function () { - MONO._next_id_var = 0; - MONO._id_table = {}; - }, - - mono_wasm_debugger_resume: function () { - this._clear_per_step_state (); - }, - - mono_wasm_detach_debugger: function () { - if (!this.mono_wasm_set_is_debugger_attached) - this.mono_wasm_set_is_debugger_attached = Module.cwrap ('mono_wasm_set_is_debugger_attached', 'void', ['bool']); - this.mono_wasm_set_is_debugger_attached(false); - }, - - _register_c_fn: function (name, ...args) { - Object.defineProperty (this._c_fn_table, name + '_wrapper', { value: Module.cwrap (name, ...args) }); - }, - - /** - * Calls `Module.cwrap` for the function name, - * and creates a wrapper around it that returns - * `{ bool result, object var_info } - * - * @param {string} name C function name - * @param {string} ret_type - * @param {string[]} params - * - * @returns {void} - */ - _register_c_var_fn: function (name, ret_type, params) { - if (ret_type !== 'bool') - throw new Error (`Bug: Expected a C function signature that returns bool`); - - this._register_c_fn (name, ret_type, params); - Object.defineProperty (this, name + '_info', { - value: function (...args) { - MONO.var_info = []; - const res_ok = MONO._c_fn_table [name + '_wrapper'] (...args); - let res = MONO.var_info; - MONO.var_info = []; - if (res_ok) { - res = this._fixup_name_value_objects (res); - return { res_ok, res }; - } - - return { res_ok, res: undefined }; - } - }); - }, - - mono_wasm_runtime_ready: function () { - MONO.mono_wasm_runtime_is_ready = true; - this._clear_per_step_state (); - - // FIXME: where should this go? - MONO._next_call_function_res_id = 0; - this._call_function_res_cache = {}; - - this._c_fn_table = {}; - this._register_c_fn ('mono_wasm_send_dbg_command', 'bool', [ 'number', 'number', 'number', 'number', 'number' ]); - this._register_c_fn ('mono_wasm_send_dbg_command_with_parms', 'bool', [ 'number', 'number', 'number', 'number', 'number', 'number', 'string' ]); - - // DO NOT REMOVE - magic debugger init function - if (globalThis.dotnetDebugger) - debugger; - else - console.debug ("mono_wasm_runtime_ready", "fe00e07a-5519-4dfe-b35a-f867dbaf2e28"); - }, - - // Set environment variable NAME to VALUE - // Should be called before mono_load_runtime_and_bcl () in most cases - mono_wasm_setenv: function (name, value) { - if (!this.wasm_setenv) - this.wasm_setenv = Module.cwrap ('mono_wasm_setenv', null, ['string', 'string']); - this.wasm_setenv (name, value); - }, - - mono_wasm_set_runtime_options: function (options) { - if (!this.wasm_parse_runtime_options) - this.wasm_parse_runtime_options = Module.cwrap ('mono_wasm_parse_runtime_options', null, ['number', 'number']); - var argv = Module._malloc (options.length * 4); - var wasm_strdup = Module.cwrap ('mono_wasm_strdup', 'number', ['string']); - let aindex = 0; - for (var i = 0; i < options.length; ++i) { - Module.setValue (argv + (aindex * 4), wasm_strdup (options [i]), "i32"); - aindex += 1; - } - this.wasm_parse_runtime_options (options.length, argv); - }, - - // - // Initialize the AOT profiler with OPTIONS. - // Requires the AOT profiler to be linked into the app. - // options = { write_at: "", send_to: "" } - // should be in the format ::. - // write_at defaults to 'WebAssembly.Runtime::StopProfile'. - // send_to defaults to 'WebAssembly.Runtime::DumpAotProfileData'. - // DumpAotProfileData stores the data into Module.aot_profile_data. - // - mono_wasm_init_aot_profiler: function (options) { - if (options == null) - options = {} - if (!('write_at' in options)) - options.write_at = 'Interop/Runtime::StopProfile'; - if (!('send_to' in options)) - options.send_to = 'Interop/Runtime::DumpAotProfileData'; - var arg = "aot:write-at-method=" + options.write_at + ",send-to-method=" + options.send_to; - Module.ccall ('mono_wasm_load_profiler_aot', null, ['string'], [arg]); - }, - - // options = { write_at: "", send_to: "" } - // should be in the format ::. - // write_at defaults to 'WebAssembly.Runtime::StopProfile'. - // send_to defaults to 'WebAssembly.Runtime::DumpCoverageProfileData'. - // DumpCoverageProfileData stores the data into Module.coverage_profile_data. - mono_wasm_init_coverage_profiler: function (options) { - if (options == null) - options = {} - if (!('write_at' in options)) - options.write_at = 'WebAssembly.Runtime::StopProfile'; - if (!('send_to' in options)) - options.send_to = 'WebAssembly.Runtime::DumpCoverageProfileData'; - var arg = "coverage:write-at-method=" + options.write_at + ",send-to-method=" + options.send_to; - Module.ccall ('mono_wasm_load_profiler_coverage', null, ['string'], [arg]); - }, - - _apply_configuration_from_args: function (args) { - for (var k in (args.environment_variables || {})) - MONO.mono_wasm_setenv (k, args.environment_variables[k]); - - if (args.runtime_options) - MONO.mono_wasm_set_runtime_options (args.runtime_options); - - if (args.aot_profiler_options) - MONO.mono_wasm_init_aot_profiler (args.aot_profiler_options); - - if (args.coverage_profiler_options) - MONO.mono_wasm_init_coverage_profiler (args.coverage_profiler_options); - }, - - _get_fetch_file_cb_from_args: function (args) { - if (typeof (args.fetch_file_cb) === "function") - return args.fetch_file_cb; - - if (ENVIRONMENT_IS_NODE) { - var fs = require('fs'); - return function (asset) { - console.debug ("MONO_WASM: Loading... " + asset); - var binary = fs.readFileSync (asset); - var resolve_func2 = function (resolve, reject) { - resolve (new Uint8Array (binary)); - }; - - var resolve_func1 = function (resolve, reject) { - var response = { - ok: true, - url: asset, - arrayBuffer: function () { - return new Promise (resolve_func2); - } - }; - resolve (response); - }; - - return new Promise (resolve_func1); - }; - } else if (typeof (fetch) === "function") { - return function (asset) { - return fetch (asset, { credentials: 'same-origin' }); - }; - } else { - throw new Error ("No fetch_file_cb was provided and this environment does not expose 'fetch'."); - } - }, - - _handle_loaded_asset: function (ctx, asset, url, blob) { - var bytes = new Uint8Array (blob); - if (ctx.tracing) - console.log ("MONO_WASM: Loaded:", asset.name, "size", bytes.length, "from", url); - - var virtualName = asset.virtual_path || asset.name; - var offset = null; - - switch (asset.behavior) { - case "resource": - case "assembly": - ctx.loaded_files.push ({ url: url, file: virtualName}); - case "heap": - case "icu": - offset = this.mono_wasm_load_bytes_into_heap (bytes); - ctx.loaded_assets[virtualName] = [offset, bytes.length]; - break; - - case "vfs": - // FIXME - var lastSlash = virtualName.lastIndexOf("/"); - var parentDirectory = (lastSlash > 0) - ? virtualName.substr(0, lastSlash) - : null; - var fileName = (lastSlash > 0) - ? virtualName.substr(lastSlash + 1) - : virtualName; - if (fileName.startsWith("/")) - fileName = fileName.substr(1); - if (parentDirectory) { - if (ctx.tracing) - console.log ("MONO_WASM: Creating directory '" + parentDirectory + "'"); - - var pathRet = ctx.createPath( - "/", parentDirectory, true, true // fixme: should canWrite be false? - ); - } else { - parentDirectory = "/"; - } - - if (ctx.tracing) - console.log ("MONO_WASM: Creating file '" + fileName + "' in directory '" + parentDirectory + "'"); - - if (!this.mono_wasm_load_data_archive (bytes, parentDirectory)) { - var fileRet = ctx.createDataFile ( - parentDirectory, fileName, - bytes, true /* canRead */, true /* canWrite */, true /* canOwn */ - ); - } - break; - - default: - throw new Error ("Unrecognized asset behavior:", asset.behavior, "for asset", asset.name); - } - - if (asset.behavior === "assembly") { - var hasPpdb = ctx.mono_wasm_add_assembly (virtualName, offset, bytes.length); - - if (!hasPpdb) { - var index = ctx.loaded_files.findIndex(element => element.file == virtualName); - ctx.loaded_files.splice(index, 1); - } - } - else if (asset.behavior === "icu") { - if (this.mono_wasm_load_icu_data (offset)) - ctx.num_icu_assets_loaded_successfully += 1; - else - console.error ("Error loading ICU asset", asset.name); - } - else if (asset.behavior === "resource") { - ctx.mono_wasm_add_satellite_assembly (virtualName, asset.culture, offset, bytes.length); - } - }, - - // deprecated - mono_load_runtime_and_bcl: function ( - unused_vfs_prefix, deploy_prefix, debug_level, file_list, loaded_cb, fetch_file_cb - ) { - var args = { - fetch_file_cb: fetch_file_cb, - loaded_cb: loaded_cb, - debug_level: debug_level, - assembly_root: deploy_prefix, - assets: [] - }; - - for (var i = 0; i < file_list.length; i++) { - var file_name = file_list[i]; - var behavior; - if (file_name.startsWith ("icudt") && file_name.endsWith (".dat")) { - // ICU data files are expected to be "icudt%FilterName%.dat" - behavior = "icu"; - } else { // if (file_name.endsWith (".pdb") || file_name.endsWith (".dll")) - behavior = "assembly"; - } - - args.assets.push ({ - name: file_name, - behavior: behavior - }); - } - - return this.mono_load_runtime_and_bcl_args (args); - }, - - // Initializes the runtime and loads assemblies, debug information, and other files. - // @args is a dictionary-style Object with the following properties: - // assembly_root: (required) the subfolder containing managed assemblies and pdbs - // debug_level or enable_debugging: (required) - // assets: (required) a list of assets to load along with the runtime. each asset - // is a dictionary-style Object with the following properties: - // name: (required) the name of the asset, including extension. - // behavior: (required) determines how the asset will be handled once loaded: - // "heap": store asset into the native heap - // "assembly": load asset as a managed assembly (or debugging information) - // "resource": load asset as a managed resource assembly - // "icu": load asset as an ICU data archive - // "vfs": load asset into the virtual filesystem (for fopen, File.Open, etc) - // load_remote: (optional) if true, an attempt will be made to load the asset - // from each location in @args.remote_sources. - // virtual_path: (optional) if specified, overrides the path of the asset in - // the virtual filesystem and similar data structures once loaded. - // is_optional: (optional) if true, any failure to load this asset will be ignored. - // loaded_cb: (required) a function () invoked when loading has completed. - // fetch_file_cb: (optional) a function (string) invoked to fetch a given file. - // If no callback is provided a default implementation appropriate for the current - // environment will be selected (readFileSync in node, fetch elsewhere). - // If no default implementation is available this call will fail. - // remote_sources: (optional) additional search locations for assets. - // sources will be checked in sequential order until the asset is found. - // the string "./" indicates to load from the application directory (as with the - // files in assembly_list), and a fully-qualified URL like "https://example.com/" indicates - // that asset loads can be attempted from a remote server. Sources must end with a "/". - // environment_variables: (optional) dictionary-style Object containing environment variables - // runtime_options: (optional) array of runtime options as strings - // aot_profiler_options: (optional) dictionary-style Object. see the comments for - // mono_wasm_init_aot_profiler. If omitted, aot profiler will not be initialized. - // coverage_profiler_options: (optional) dictionary-style Object. see the comments for - // mono_wasm_init_coverage_profiler. If omitted, coverage profiler will not be initialized. - // globalization_mode: (optional) configures the runtime's globalization mode: - // "icu": load ICU globalization data from any runtime assets with behavior "icu". - // "invariant": operate in invariant globalization mode. - // "auto" (default): if "icu" behavior assets are present, use ICU, otherwise invariant. - // diagnostic_tracing: (optional) enables diagnostic log messages during startup - mono_load_runtime_and_bcl_args: function (args) { - try { - return this._load_assets_and_runtime (args); - } catch (exc) { - console.error ("error in mono_load_runtime_and_bcl_args:", exc); - throw exc; - } - }, - - // @bytes must be a typed array. space is allocated for it in the native heap - // and it is copied to that location. returns the address of the allocation. - mono_wasm_load_bytes_into_heap: function (bytes) { - var memoryOffset = Module._malloc (bytes.length); - var heapBytes = new Uint8Array (Module.HEAPU8.buffer, memoryOffset, bytes.length); - heapBytes.set (bytes); - return memoryOffset; - }, - - // @offset must be the address of an ICU data archive in the native heap. - // returns true on success. - mono_wasm_load_icu_data: function (offset) { - var fn = Module.cwrap ('mono_wasm_load_icu_data', 'number', ['number']); - var ok = (fn (offset)) === 1; - if (ok) - MONO.num_icu_assets_loaded_successfully++; - return ok; - }, - - // Get icudt.dat exact filename that matches given culture, examples: - // "ja" -> "icudt_CJK.dat" - // "en_US" (or "en-US" or just "en") -> "icudt_EFIGS.dat" - // etc, see "mono_wasm_get_icudt_name" implementation in pal_icushim_static.c - mono_wasm_get_icudt_name: function (culture) { - return Module.ccall ('mono_wasm_get_icudt_name', 'string', ['string'], [culture]); - }, - - _finalize_startup: function (args, ctx) { - var loaded_files_with_debug_info = []; - - MONO.loaded_assets = ctx.loaded_assets; - ctx.loaded_files.forEach(value => loaded_files_with_debug_info.push(value.url)); - MONO.loaded_files = loaded_files_with_debug_info; - 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)); - } - - var load_runtime = Module.cwrap ('mono_wasm_load_runtime', null, ['string', 'number']); - - console.debug ("MONO_WASM: Initializing mono runtime"); - - this.mono_wasm_globalization_init (args.globalization_mode); - - if (ENVIRONMENT_IS_SHELL || ENVIRONMENT_IS_NODE) { - try { - load_runtime ("unused", args.debug_level); - } catch (ex) { - print ("MONO_WASM: load_runtime () failed: " + ex); - print ("MONO_WASM: Stacktrace: \n"); - print (ex.stack); - - var wasm_exit = Module.cwrap ('mono_wasm_exit', null, ['number']); - wasm_exit (1); - } - } else { - load_runtime ("unused", args.debug_level); - } - - let tz; - try { - tz = Intl.DateTimeFormat().resolvedOptions().timeZone; - } catch {} - MONO.mono_wasm_setenv ("TZ", tz || "UTC"); - MONO.mono_wasm_runtime_ready (); - args.loaded_cb (); - }, - - _load_assets_and_runtime: function (args) { - if (args.enable_debugging) - args.debug_level = args.enable_debugging; - if (args.assembly_list) - throw new Error ("Invalid args (assembly_list was replaced by assets)"); - if (args.runtime_assets) - throw new Error ("Invalid args (runtime_assets was replaced by assets)"); - if (args.runtime_asset_sources) - throw new Error ("Invalid args (runtime_asset_sources was replaced by remote_sources)"); - if (!args.loaded_cb) - throw new Error ("loaded_cb not provided"); - - var ctx = { - tracing: args.diagnostic_tracing || false, - pending_count: args.assets.length, - mono_wasm_add_assembly: Module.cwrap ('mono_wasm_add_assembly', 'number', ['string', 'number', 'number']), - mono_wasm_add_satellite_assembly: Module.cwrap ('mono_wasm_add_satellite_assembly', 'void', ['string', 'string', 'number', 'number']), - loaded_assets: Object.create (null), - // dlls and pdbs, used by blazor and the debugger - loaded_files: [], - createPath: Module['FS_createPath'], - createDataFile: Module['FS_createDataFile'] - }; - - if (ctx.tracing) - console.log ("mono_wasm_load_runtime_with_args", JSON.stringify(args)); - - this._apply_configuration_from_args (args); - - var fetch_file_cb = this._get_fetch_file_cb_from_args (args); - - var onPendingRequestComplete = function () { - --ctx.pending_count; - - if (ctx.pending_count === 0) { - try { - MONO._finalize_startup (args, ctx); - } catch (exc) { - console.error ("Unhandled exception in _finalize_startup", exc); - throw exc; - } - } - }; - - var processFetchResponseBuffer = function (asset, url, blob) { - try { - MONO._handle_loaded_asset (ctx, asset, url, blob); - } catch (exc) { - console.error ("Unhandled exception in processFetchResponseBuffer", exc); - throw exc; - } finally { - onPendingRequestComplete (); - } - }; - - args.assets.forEach (function (asset) { - var attemptNextSource; - var sourceIndex = 0; - var sourcesList = asset.load_remote ? args.remote_sources : [""]; - - var handleFetchResponse = function (response) { - if (!response.ok) { - try { - attemptNextSource (); - return; - } catch (exc) { - console.error ("MONO_WASM: Unhandled exception in handleFetchResponse attemptNextSource for asset", asset.name, exc); - throw exc; - } - } - - try { - var bufferPromise = response ['arrayBuffer'] (); - bufferPromise.then (processFetchResponseBuffer.bind (this, asset, response.url)); - } catch (exc) { - console.error ("MONO_WASM: Unhandled exception in handleFetchResponse for asset", asset.name, exc); - attemptNextSource (); - } - }; - - attemptNextSource = function () { - if (sourceIndex >= sourcesList.length) { - var msg = "MONO_WASM: Failed to load " + asset.name; - try { - var isOk = asset.is_optional || - (asset.name.match (/\.pdb$/) && MONO.mono_wasm_ignore_pdb_load_errors); - - if (isOk) - console.debug (msg); - else { - console.error (msg); - throw new Error (msg); - } - } finally { - onPendingRequestComplete (); - } - } - - var sourcePrefix = sourcesList[sourceIndex]; - sourceIndex++; - - // HACK: Special-case because MSBuild doesn't allow "" as an attribute - if (sourcePrefix === "./") - sourcePrefix = ""; - - var attemptUrl; - if (sourcePrefix.trim() === "") { - if (asset.behavior === "assembly") - attemptUrl = locateFile (args.assembly_root + "/" + asset.name); - else if (asset.behavior === "resource") { - var path = asset.culture !== '' ? `${asset.culture}/${asset.name}` : asset.name; - attemptUrl = locateFile (args.assembly_root + "/" + path); - } - else - attemptUrl = asset.name; - } else { - attemptUrl = sourcePrefix + asset.name; - } - - try { - if (asset.name === attemptUrl) { - if (ctx.tracing) - console.log ("Attempting to fetch '" + attemptUrl + "'"); - } else { - if (ctx.tracing) - console.log ("Attempting to fetch '" + attemptUrl + "' for", asset.name); - } - var fetch_promise = fetch_file_cb (attemptUrl); - fetch_promise.then (handleFetchResponse); - } catch (exc) { - console.error ("MONO_WASM: Error fetching " + attemptUrl, exc); - attemptNextSource (); - } - }; - - attemptNextSource (); - }); - }, - - // Performs setup for globalization. - // @globalization_mode is one of "icu", "invariant", or "auto". - // "auto" will use "icu" if any ICU data archives have been loaded, - // otherwise "invariant". - mono_wasm_globalization_init: function (globalization_mode) { - var invariantMode = false; - - if (globalization_mode === "invariant") - invariantMode = true; - - if (!invariantMode) { - if (MONO.num_icu_assets_loaded_successfully > 0) { - 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"); - invariantMode = true; - } else { - var msg = "invariant globalization mode is inactive and no ICU data archives were loaded"; - console.error ("MONO_WASM: ERROR: " + msg); - throw new Error (msg); - } - } - - if (invariantMode) - this.mono_wasm_setenv ("DOTNET_SYSTEM_GLOBALIZATION_INVARIANT", "1"); - - // Set globalization mode to PredefinedCulturesOnly - this.mono_wasm_setenv ("DOTNET_SYSTEM_GLOBALIZATION_PREDEFINED_CULTURES_ONLY", "1"); - }, - - // Used by the debugger to enumerate loaded dlls and pdbs - mono_wasm_get_loaded_files: function() { - if (!this.mono_wasm_set_is_debugger_attached) - this.mono_wasm_set_is_debugger_attached = Module.cwrap ('mono_wasm_set_is_debugger_attached', 'void', ['bool']); - this.mono_wasm_set_is_debugger_attached (true); - return MONO.loaded_files; - }, - - mono_wasm_get_loaded_asset_table: function() { - return MONO.loaded_assets; - }, - - // FIXME: improve - _base64_to_uint8: function (base64String) { - const byteCharacters = atob (base64String); - const byteNumbers = new Array(byteCharacters.length); - for (let i = 0; i < byteCharacters.length; i++) { - byteNumbers[i] = byteCharacters.charCodeAt(i); - } - - return new Uint8Array (byteNumbers); - }, - - mono_wasm_load_data_archive: function (data, prefix) { - if (data.length < 8) - return false; - - var dataview = new DataView(data.buffer); - var magic = dataview.getUint32(0, true); - // get magic number - if (magic != 0x626c6174) { - return false; - } - var manifestSize = dataview.getUint32(4, true); - if (manifestSize == 0 || data.length < manifestSize + 8) - return false; - - var manifest; - try { - var manifestContent = Module.UTF8ArrayToString(data, 8, manifestSize); - manifest = JSON.parse(manifestContent); - if (!(manifest instanceof Array)) - return false; - } catch (exc) { - return false; - } - - data = data.slice(manifestSize+8); - - // Create the folder structure - // /usr/share/zoneinfo - // /usr/share/zoneinfo/Africa - // /usr/share/zoneinfo/Asia - // .. - - var folders = new Set() - manifest.filter(m => { - var file = m[0]; - var last = file.lastIndexOf ("/"); - var directory = file.slice (0, last+1); - folders.add(directory); - }); - folders.forEach(folder => { - Module['FS_createPath'](prefix, folder, true, true); - }); - - for (var row of manifest) { - var name = row[0]; - var length = row[1]; - var bytes = data.slice(0, length); - Module['FS_createDataFile'](prefix, name, bytes, true, true); - data = data.slice(length); - } - return true; - }, - - /** - * Raises an event for the debug proxy - * - * @param {Event} event - event to be raised - * @param {object} args - arguments for raising this event, eg. `{trace: true}` - */ - mono_wasm_raise_debug_event: function(event, args={}) { - if (typeof event !== 'object') - throw new Error(`event must be an object, but got ${JSON.stringify(event)}`); - - if (event.eventName === undefined) - throw new Error(`event.eventName is a required parameter, in event: ${JSON.stringify(event)}`); - - if (typeof args !== 'object') - throw new Error(`args must be an object, but got ${JSON.stringify(args)}`); - - console.debug('mono_wasm_debug_event_raised:aef14bca-5519-4dfe-b35a-f867abc123ae', JSON.stringify(event), JSON.stringify(args)); - }, - - /** - * 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 - */ - mono_wasm_load_config: async function (configFilePath) { - Module.addRunDependency(configFilePath); - try { - let config = null; - // NOTE: when we add nodejs make sure to include the nodejs fetch package - if (ENVIRONMENT_IS_WEB) { - const configRaw = await fetch(configFilePath); - config = await configRaw.json(); - }else if (ENVIRONMENT_IS_NODE) { - config = require(configFilePath); - } else { // shell or worker - config = JSON.parse(read(configFilePath)); // read is a v8 debugger command - } - Module.config = config; - } catch(e) { - Module.config = {message: "failed to load config file", error: e}; - } finally { - Module.removeRunDependency(configFilePath); - } - }, - mono_wasm_set_timeout_exec: function(id){ - if (!this.mono_set_timeout_exec) - this.mono_set_timeout_exec = Module.cwrap ("mono_set_timeout_exec", null, [ 'number' ]); - this.mono_set_timeout_exec (id); - }, - prevent_timer_throttling: function () { - if (!MONO.isChromium) { - return; - } - - // this will schedule timers every second for next 6 minutes, it should be called from WebSocket event, to make it work - // on next call, it would only extend the timers to cover yet uncovered future - let now = new Date().valueOf(); - const desired_reach_time = now + (1000 * 60 * 6); - const next_reach_time = Math.max(now + 1000, this.spread_timers_maximum); - const light_throttling_frequency = 1000; - for (var schedule = next_reach_time; schedule < desired_reach_time; schedule += light_throttling_frequency) { - const delay = schedule - now; - setTimeout(() => { - this.mono_wasm_set_timeout_exec(0); - MONO.pump_count++; - MONO.pump_message(); - }, delay); - } - this.spread_timers_maximum = desired_reach_time; - } - }, - schedule_background_exec: function () { - ++MONO.pump_count; - if (typeof globalThis.setTimeout === 'function') { - globalThis.setTimeout (MONO.pump_message, 0); - } - }, - - mono_set_timeout: function (timeout, id) { - - if (typeof globalThis.setTimeout === 'function') { - globalThis.setTimeout (function () { - MONO.mono_wasm_set_timeout_exec (id); - }, timeout); - } else { - ++MONO.pump_count; - MONO.timeout_queue.push(function() { - MONO.mono_wasm_set_timeout_exec (id); - }) - } - }, - - mono_wasm_fire_debugger_agent_message: function () { - // eslint-disable-next-line no-debugger - debugger; - }, - - mono_wasm_asm_loaded: function (assembly_name, assembly_ptr, assembly_len, pdb_ptr, pdb_len) { - // Only trigger this codepath for assemblies loaded after app is ready - if (MONO.mono_wasm_runtime_is_ready !== true) - return; - - const assembly_name_str = assembly_name !== 0 ? Module.UTF8ToString(assembly_name).concat('.dll') : ''; - - const assembly_data = new Uint8Array(Module.HEAPU8.buffer, assembly_ptr, assembly_len); - const assembly_b64 = MONO._base64Converter.toBase64StringImpl(assembly_data); - - let pdb_b64; - if (pdb_ptr) { - const pdb_data = new Uint8Array(Module.HEAPU8.buffer, pdb_ptr, pdb_len); - pdb_b64 = MONO._base64Converter.toBase64StringImpl(pdb_data); - } - - MONO.mono_wasm_raise_debug_event({ - eventName: 'AssemblyLoaded', - assembly_name: assembly_name_str, - assembly_b64, - pdb_b64 - }); - } +const MonoSupportLib = { + // this line will be executed in mergeInto below + $MONO__postset: '__dotnet_runtime.export_functions(MONO, Module);', + // this will become globalThis.MONO + $MONO: {}, + // the methods would be visible to EMCC linker + mono_set_timeout: function () { return MONO.mono_set_timeout.call(MONO, arguments) }, + mono_wasm_asm_loaded: function () { return MONO.mono_wasm_asm_loaded.call(MONO, arguments) }, + mono_wasm_fire_debugger_agent_message: function () { return MONO.mono_wasm_fire_debugger_agent_message.call(MONO, arguments) }, + schedule_background_exec: function () { return MONO.schedule_background_exec.call(MONO, arguments) }, }; autoAddDeps(MonoSupportLib, '$MONO') diff --git a/src/mono/wasm/runtime/package-lock.json b/src/mono/wasm/runtime/package-lock.json new file mode 100644 index 0000000000000..60dd9a6b6f885 --- /dev/null +++ b/src/mono/wasm/runtime/package-lock.json @@ -0,0 +1,328 @@ +{ + "name": "dotnet-runtime", + "version": "1.0.0", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "@babel/code-frame": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.14.5.tgz", + "integrity": "sha512-9pzDqyc6OLDaqe+zbACgFkb6fKMNG6CObKpnYXChRsvYGyEdc7CA2BaqeOM+vOtCS5ndmJicPJhKAwYRI6UfFw==", + "dev": true, + "requires": { + "@babel/highlight": "^7.14.5" + } + }, + "@babel/helper-validator-identifier": { + "version": "7.15.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.15.7.tgz", + "integrity": "sha512-K4JvCtQqad9OY2+yTU8w+E82ywk/fe+ELNlt1G8z3bVGlZfn/hOcQQsUhGhW/N+tb3fxK800wLtKOE/aM0m72w==", + "dev": true + }, + "@babel/highlight": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.14.5.tgz", + "integrity": "sha512-qf9u2WFWVV0MppaL877j2dBtQIDgmidgjGk5VIMw3OadXvYaXn66U1BFlH2t4+t3i+8PhedppRv+i40ABzd+gg==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.14.5", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + } + }, + "@rollup/plugin-typescript": { + "version": "8.2.5", + "resolved": "https://registry.npmjs.org/@rollup/plugin-typescript/-/plugin-typescript-8.2.5.tgz", + "integrity": "sha512-QL/LvDol/PAGB2O0S7/+q2HpSUNodpw7z6nGn9BfoVCPOZ0r4EALrojFU29Bkoi2Hr2jgTocTejJ5GGWZfOxbQ==", + "dev": true, + "requires": { + "@rollup/pluginutils": "^3.1.0", + "resolve": "^1.17.0" + } + }, + "@rollup/pluginutils": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-3.1.0.tgz", + "integrity": "sha512-GksZ6pr6TpIjHm8h9lSQ8pi8BE9VeubNT0OMJ3B5uZJ8pz73NPiqOtCog/x2/QzM1ENChPKxMDhiQuRHsqc+lg==", + "dev": true, + "requires": { + "@types/estree": "0.0.39", + "estree-walker": "^1.0.1", + "picomatch": "^2.2.2" + }, + "dependencies": { + "@types/estree": { + "version": "0.0.39", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.39.tgz", + "integrity": "sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw==", + "dev": true + } + } + }, + "@types/node": { + "version": "16.9.4", + "resolved": "https://registry.npmjs.org/@types/node/-/node-16.9.4.tgz", + "integrity": "sha512-KDazLNYAGIuJugdbULwFZULF9qQ13yNWEBFnfVpqlpgAAo6H/qnM9RjBgh0A0kmHf3XxAKLdN5mTIng9iUvVLA==", + "dev": true + }, + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "dev": true + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "dev": true + }, + "commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "dev": true + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", + "dev": true + }, + "estree-walker": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-1.0.1.tgz", + "integrity": "sha512-1fMXF3YP4pZZVozF8j/ZLfvnR8NSIljt56UhbZ5PeeDmmGHpgpdwQt7ITlGvYaQukCvuBRMLEiKiYC+oeIg4cg==", + "dev": true + }, + "fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "optional": true + }, + "function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", + "dev": true + }, + "has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dev": true, + "requires": { + "function-bind": "^1.1.1" + } + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, + "is-core-module": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.6.0.tgz", + "integrity": "sha512-wShG8vs60jKfPWpF2KZRaAtvt3a20OAn7+IJ6hLPECpSABLcKtFKTTI4ZtH5QcBruBHlq+WsdHWyz0BCZW7svQ==", + "dev": true, + "requires": { + "has": "^1.0.3" + } + }, + "jest-worker": { + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-26.6.2.tgz", + "integrity": "sha512-KWYVV1c4i+jbMpaBC+U++4Va0cp8OisU185o73T1vo99hqi7w8tSJfUXYswwqqrjzwxa6KpRK54WhPvwf5w6PQ==", + "dev": true, + "requires": { + "@types/node": "*", + "merge-stream": "^2.0.0", + "supports-color": "^7.0.0" + }, + "dependencies": { + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true + }, + "merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true + }, + "path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true + }, + "picomatch": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.0.tgz", + "integrity": "sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw==", + "dev": true + }, + "randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "dev": true, + "requires": { + "safe-buffer": "^5.1.0" + } + }, + "resolve": { + "version": "1.20.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.20.0.tgz", + "integrity": "sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A==", + "dev": true, + "requires": { + "is-core-module": "^2.2.0", + "path-parse": "^1.0.6" + } + }, + "rollup": { + "version": "2.56.3", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.56.3.tgz", + "integrity": "sha512-Au92NuznFklgQCUcV96iXlxUbHuB1vQMaH76DHl5M11TotjOHwqk9CwcrT78+Tnv4FN9uTBxq6p4EJoYkpyekg==", + "dev": true, + "requires": { + "fsevents": "~2.3.2" + } + }, + "rollup-plugin-terser": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/rollup-plugin-terser/-/rollup-plugin-terser-7.0.2.tgz", + "integrity": "sha512-w3iIaU4OxcF52UUXiZNsNeuXIMDvFrr+ZXK6bFZ0Q60qyVfq4uLptoS4bbq3paG3x216eQllFZX7zt6TIImguQ==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.10.4", + "jest-worker": "^26.2.1", + "serialize-javascript": "^4.0.0", + "terser": "^5.0.0" + } + }, + "safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true + }, + "serialize-javascript": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-4.0.0.tgz", + "integrity": "sha512-GaNA54380uFefWghODBWEGisLZFj00nS5ACs6yHa9nLqlLpVLO8ChDGeKRjZnV4Nh4n0Qi7nhYZD/9fCPzEqkw==", + "dev": true, + "requires": { + "randombytes": "^2.1.0" + } + }, + "source-map": { + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz", + "integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==", + "dev": true + }, + "source-map-support": { + "version": "0.5.20", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.20.tgz", + "integrity": "sha512-n1lZZ8Ve4ksRqizaBQgxXDgKwttHDhyfQjA6YZZn8+AroHbsIz+JjwxQDxbp+7y5OYCI8t1Yk7etjD9CRd2hIw==", + "dev": true, + "requires": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } + } + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + }, + "terser": { + "version": "5.8.0", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.8.0.tgz", + "integrity": "sha512-f0JH+6yMpneYcRJN314lZrSwu9eKkUFEHLN/kNy8ceh8gaRiLgFPJqrB9HsXjhEGdv4e/ekjTOFxIlL6xlma8A==", + "dev": true, + "requires": { + "commander": "^2.20.0", + "source-map": "~0.7.2", + "source-map-support": "~0.5.20" + } + }, + "tslib": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz", + "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==", + "dev": true + }, + "typescript": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.4.3.tgz", + "integrity": "sha512-4xfscpisVgqqDfPaJo5vkd+Qd/ItkoagnHpufr+i2QCHBsNYp+G7UAoyFl8aPtx879u38wPV65rZ8qbGZijalA==", + "dev": true + } + } +} diff --git a/src/mono/wasm/runtime/package.json b/src/mono/wasm/runtime/package.json new file mode 100644 index 0000000000000..16199c17c40e8 --- /dev/null +++ b/src/mono/wasm/runtime/package.json @@ -0,0 +1,26 @@ +{ + "name": "dotnet-runtime", + "description": ".NET is a developer platform with tools and libraries for building any type of app, including web, mobile, desktop, games, IoT, cloud, and microservices.", + "repository": { + "type": "git", + "url": "git@github.com:dotnet/runtime.git" + }, + "version": "1.0.0", + "scripts": { + "rollup": "rollup -c" + }, + "keywords": [ + "dotnet", + "runtime", + "wasm" + ], + "author": "Microsoft", + "license": "MIT", + "devDependencies": { + "@rollup/plugin-typescript": "8.2.5", + "rollup": "2.56.3", + "rollup-plugin-terser": "7.0.2", + "tslib": "2.3.1", + "typescript": "4.4.3" + } +} diff --git a/src/mono/wasm/runtime/rollup.config.js b/src/mono/wasm/runtime/rollup.config.js new file mode 100644 index 0000000000000..7b2777118ae96 --- /dev/null +++ b/src/mono/wasm/runtime/rollup.config.js @@ -0,0 +1,69 @@ +import { defineConfig } from 'rollup'; +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 { createHash } from 'crypto'; + +const outputFileName = 'runtime.iffe.js' +const isDebug = process.env.Configuration !== 'Release'; +const nativeBinDir = process.env.NativeBinDir ? process.env.NativeBinDir.replace(/\"/g, '') : 'bin'; +const plugins = isDebug ? [writeOnChangePlugin()] : [terser(), writeOnChangePlugin()] + +export default defineConfig({ + treeshake: !isDebug, + input: 'src/startup.ts', + output: [{ + 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', + name: '__dotnet_runtime', + file: nativeBinDir + '/src/' + outputFileName, + + // emcc doesn't know how to load ES6 module, that's why we need the whole rollup.js + format: 'iife', + plugins: plugins + }], + plugins: [typescript()] +}); + +// this would create .md5 file next to the output file, so that we do not touch datetime of the file if it's same -> faster incremental build. +function writeOnChangePlugin() { + return { + name: 'writeOnChange', + generateBundle: writeWhenChanged + } +} + +async function writeWhenChanged(options, bundle) { + try { + const asset = bundle[outputFileName]; + const code = asset.code; + const hashFileName = options.file + '.sha256'; + const oldHashExists = await checkFileExists(hashFileName); + const oldFileExists = await checkFileExists(options.file) + + var newHash = createHash('sha256').update(code).digest('hex'); + + let isOutputChanged = true; + if (oldHashExists && oldFileExists) { + const oldHash = await readFile(hashFileName, { encoding: 'ascii' }); + isOutputChanged = oldHash !== newHash + } + if (isOutputChanged) { + if (!await checkFileExists(hashFileName)) { + await mkdir(nativeBinDir + '/src', { recursive: true }); + } + await writeFile(hashFileName, newHash); + } else { + // this.warn('No change in ' + options.file) + delete bundle[outputFileName] + } + } catch (ex) { + this.warn(ex.toString()); + } +} + +function checkFileExists(file) { + return fs.promises.access(file, fs.constants.F_OK) + .then(() => true) + .catch(() => false) +} \ No newline at end of file diff --git a/src/mono/wasm/runtime/src/binding/types.ts b/src/mono/wasm/runtime/src/binding/types.ts new file mode 100644 index 0000000000000..c8c38ce13487b --- /dev/null +++ b/src/mono/wasm/runtime/src/binding/types.ts @@ -0,0 +1,14 @@ +/* TODO this is from ASP +declare interface BINDING { + mono_obj_array_new(length: number): System_Array; + mono_obj_array_set(array: System_Array, index: Number, value: System_Object): void; + js_string_to_mono_string(jsString: string): System_String; + js_typed_array_to_array(array: Uint8Array): System_Object; + js_to_mono_obj(jsObject: any) : System_Object; + mono_array_to_js_array(array: System_Array) : Array; + conv_string(dotnetString: System_String | null): string | null; + bind_static_method(fqn: string, signature?: string): Function; + call_assembly_entry_point(assemblyName: string, args: any[], signature: any): Promise; + unbox_mono_obj(object: System_Object): any; + } + */ \ No newline at end of file diff --git a/src/mono/wasm/runtime/src/mono/base64.ts b/src/mono/wasm/runtime/src/mono/base64.ts new file mode 100644 index 0000000000000..322b0f72700b7 --- /dev/null +++ b/src/mono/wasm/runtime/src/mono/base64.ts @@ -0,0 +1,121 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +// Code from JSIL: +// https://github.com/sq/JSIL/blob/1d57d5427c87ab92ffa3ca4b82429cd7509796ba/JSIL.Libraries/Includes/Bootstrap/Core/Classes/System.Convert.js#L149 +// Thanks to Katelyn Gadd @kg + +export function toBase64StringImpl(inArray: Uint8Array, offset?: number, length?: number) { + var reader = _makeByteReader(inArray, offset, length); + var result = ""; + var ch1: number | null = 0, ch2: number | null = 0, ch3: number | null = 0 + var bits = 0, equalsCount = 0, sum = 0; + var mask1 = (1 << 24) - 1, mask2 = (1 << 18) - 1, mask3 = (1 << 12) - 1, mask4 = (1 << 6) - 1; + var shift1 = 18, shift2 = 12, shift3 = 6, shift4 = 0; + + while (true) { + ch1 = reader.read(); + ch2 = reader.read(); + ch3 = reader.read(); + + if (ch1 === null) + break; + if (ch2 === null) { + ch2 = 0; + equalsCount += 1; + } + if (ch3 === null) { + ch3 = 0; + equalsCount += 1; + } + + // Seems backwards, but is right! + sum = (ch1 << 16) | (ch2 << 8) | (ch3 << 0); + + bits = (sum & mask1) >> shift1; + result += _base64Table[bits]; + bits = (sum & mask2) >> shift2; + result += _base64Table[bits]; + + if (equalsCount < 2) { + bits = (sum & mask3) >> shift3; + result += _base64Table[bits]; + } + + if (equalsCount === 2) { + result += "=="; + } else if (equalsCount === 1) { + result += "="; + } else { + bits = (sum & mask4) >> shift4; + result += _base64Table[bits]; + } + } + + return result; +} + +const _base64Table = [ + 'A', 'B', 'C', 'D', + 'E', 'F', 'G', 'H', + 'I', 'J', 'K', 'L', + 'M', 'N', 'O', 'P', + 'Q', 'R', 'S', 'T', + 'U', 'V', 'W', 'X', + 'Y', 'Z', + 'a', 'b', 'c', 'd', + 'e', 'f', 'g', 'h', + 'i', 'j', 'k', 'l', + 'm', 'n', 'o', 'p', + 'q', 'r', 's', 't', + 'u', 'v', 'w', 'x', + 'y', 'z', + '0', '1', '2', '3', + '4', '5', '6', '7', + '8', '9', + '+', '/' +] + +function _makeByteReader(bytes: Uint8Array, index?: number, count?: number): { + read: () => number | null +} { + var position = (typeof (index) === "number") ? index : 0; + var endpoint: number; + + if (typeof (count) === "number") + endpoint = (position + count); + else + endpoint = (bytes.length - position); + + var result = { + read: function () { + if (position >= endpoint) + return null; + + var nextByte = bytes[position]; + position += 1; + return nextByte; + } + }; + + Object.defineProperty(result, "eof", { + get: function () { + return (position >= endpoint); + }, + configurable: true, + enumerable: true + }); + + return result; +} + +// FIXME: improve +export function _base64_to_uint8(base64String: string) { + const byteCharacters = atob(base64String); + const byteNumbers = new Array(byteCharacters.length); + for (let i = 0; i < byteCharacters.length; i++) { + byteNumbers[i] = byteCharacters.charCodeAt(i); + } + + return new Uint8Array(byteNumbers); +} \ No newline at end of file diff --git a/src/mono/wasm/runtime/src/mono/cwraps.ts b/src/mono/wasm/runtime/src/mono/cwraps.ts new file mode 100644 index 0000000000000..c55a59b2d30be --- /dev/null +++ b/src/mono/wasm/runtime/src/mono/cwraps.ts @@ -0,0 +1,57 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +import { Module } from '../runtime' + +const fn_signatures: [ident: string, returnType: string | null, argTypes?: string[], opts?: any][] = [ + ["mono_wasm_register_root", "number", ["number", "number", "string"]], + ["mono_wasm_deregister_root", null, ["number"]], + ["mono_wasm_string_get_data", null, ['number', 'number', 'number', 'number']], + ['mono_wasm_set_is_debugger_attached', 'void', ['bool']], + ['mono_wasm_send_dbg_command', 'bool', ['number', 'number', 'number', 'number', 'number']], + ['mono_wasm_send_dbg_command_with_parms', 'bool', ['number', 'number', 'number', 'number', 'number', 'number', 'string']], + ['mono_wasm_setenv', null, ['string', 'string']], + ['mono_wasm_parse_runtime_options', null, ['number', 'number']], + ['mono_wasm_strdup', 'number', ['string']], + ['mono_background_exec', null, []], + ["mono_set_timeout_exec", null, ['number']], + ['mono_wasm_load_icu_data', 'number', ['number']], + ['mono_wasm_get_icudt_name', 'string', ['string']], + ['mono_wasm_add_assembly', 'number', ['string', 'number', 'number']], + ['mono_wasm_add_satellite_assembly', 'void', ['string', 'string', 'number', 'number']], + ['mono_wasm_load_runtime', null, ['string', 'number']], + ['mono_wasm_exit', null, ['number']], +] + +export interface t_Cwraps { + mono_wasm_register_root(start: CharPtr, size: number, name: CharPtr): number; + mono_wasm_deregister_root(addr: CharPtr): void; + mono_wasm_string_get_data(string: MonoString, outChars: CharPtrPtr, outLengthBytes: Int32Ptr, outIsInterned: Int32Ptr): void; + mono_wasm_set_is_debugger_attached(value: boolean): void; + mono_wasm_send_dbg_command(id: number, command_set: number, command: number, data: VoidPtr, size: number): boolean; + mono_wasm_send_dbg_command_with_parms(id: number, command_set: number, command: number, data: VoidPtr, size: number, valtype: number, newvalue: CharPtr): boolean; + mono_wasm_setenv(name: string, value: string): void; + mono_wasm_strdup(value: string): number; + mono_wasm_parse_runtime_options(length: number, argv: VoidPtr): void; + mono_background_exec(): void; + mono_set_timeout_exec(id: number): void; + mono_wasm_load_icu_data(offset: VoidPtr): number; + mono_wasm_get_icudt_name(name: string): string; + mono_wasm_add_assembly(name: CharPtr, data: VoidPtr, size: number): number; + mono_wasm_add_satellite_assembly(name: CharPtr, culture: CharPtr, data: VoidPtr, size: number): void; + mono_wasm_load_runtime(unused: CharPtr, debug_level: number): void; + mono_wasm_exit(exit_code: number): number; +} + +const wrapped_c_functions: t_Cwraps = {} +for (let sig of fn_signatures) { + const wf: any = wrapped_c_functions; + // lazy init on first run + wf[sig[0]] = function () { + const fce = Module.cwrap(sig[0], sig[1], sig[2], sig[3]) + wf[sig[0]] = fce; + return fce.apply(undefined, arguments); + }; +} + +export default wrapped_c_functions; \ No newline at end of file diff --git a/src/mono/wasm/runtime/src/mono/debug.ts b/src/mono/wasm/runtime/src/mono/debug.ts new file mode 100644 index 0000000000000..75c15d4b87ba8 --- /dev/null +++ b/src/mono/wasm/runtime/src/mono/debug.ts @@ -0,0 +1,291 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +import { Module, MONO } from '../runtime' +import { toBase64StringImpl, _base64_to_uint8 } from './base64' +import cwraps from './cwraps' +import { mono_wasm_load_bytes_into_heap } from './roots' + +var commands_received: any; +var _call_function_res_cache: any = {} +var _next_call_function_res_id = 0; + +export function mono_wasm_runtime_ready() { + MONO.mono_wasm_runtime_is_ready = true; + + // FIXME: where should this go? + _next_call_function_res_id = 0; + _call_function_res_cache = {}; + + // DO NOT REMOVE - magic debugger init function + if ((globalThis).dotnetDebugger) + debugger; + else + console.debug("mono_wasm_runtime_ready", "fe00e07a-5519-4dfe-b35a-f867dbaf2e28"); +} + +export function mono_wasm_fire_debugger_agent_message() { + // eslint-disable-next-line no-debugger + debugger; +} + +export function mono_wasm_add_dbg_command_received(res_ok: boolean, id: number, buffer: number, buffer_len: number) { + const assembly_data = new Uint8Array(Module.HEAPU8.buffer, buffer, buffer_len); + const base64String = toBase64StringImpl(assembly_data); + const buffer_obj = { + res_ok, + res: { + id, + value: base64String + } + } + commands_received = buffer_obj; +} + +export function mono_wasm_send_dbg_command_with_parms(id: number, command_set: number, command: number, command_parameters: any, length: number, valtype: number, newvalue: number) { + const dataHeap = mono_wasm_load_bytes_into_heap(_base64_to_uint8(command_parameters)); + cwraps.mono_wasm_send_dbg_command_with_parms(id, command_set, command, dataHeap, length, valtype, newvalue.toString()); + + const { res_ok, res } = commands_received; + if (!res_ok) + throw new Error(`Failed on mono_wasm_invoke_method_debugger_agent_with_parms`); + return res; +} + +export function mono_wasm_send_dbg_command(id: number, command_set: number, command: number, command_parameters: any) { + var dataHeap = mono_wasm_load_bytes_into_heap(_base64_to_uint8(command_parameters)); + cwraps.mono_wasm_send_dbg_command(id, command_set, command, dataHeap, command_parameters.length); + + const { res_ok, res } = commands_received; + if (!res_ok) + throw new Error(`Failed on mono_wasm_send_dbg_command`); + return res; + +} + +export function mono_wasm_get_dbg_command_info() { + const { res_ok, res } = commands_received; + if (!res_ok) + throw new Error(`Failed on mono_wasm_get_dbg_command_info`); + return res; +} + +export function mono_wasm_debugger_resume() { +} + +export function mono_wasm_detach_debugger() { + cwraps.mono_wasm_set_is_debugger_attached(false); +} + +/** + * Raises an event for the debug proxy + */ +export function mono_wasm_raise_debug_event(event: WasmEvent, args = {}) { + if (typeof event !== 'object') + throw new Error(`event must be an object, but got ${JSON.stringify(event)}`); + + if (event.eventName === undefined) + throw new Error(`event.eventName is a required parameter, in event: ${JSON.stringify(event)}`); + + if (typeof args !== 'object') + throw new Error(`args must be an object, but got ${JSON.stringify(args)}`); + + console.debug('mono_wasm_debug_event_raised:aef14bca-5519-4dfe-b35a-f867abc123ae', JSON.stringify(event), JSON.stringify(args)); +} + +// Used by the debugger to enumerate loaded dlls and pdbs +export function mono_wasm_get_loaded_files() { + cwraps.mono_wasm_set_is_debugger_attached(true); + return MONO.loaded_files; +} + +function _create_proxy_from_object_id(objectId: string, details: any) { + if (objectId.startsWith('dotnet:array:')) { + const ret = details.map((p: any) => p.value); + return ret; + } + + const proxy: any = {}; + Object.keys(details).forEach(p => { + var prop = details[p]; + if (prop.get !== undefined) { + Object.defineProperty(proxy, + prop.name, + { + get() { + return mono_wasm_send_dbg_command(-1, prop.get.commandSet, prop.get.command, prop.get.buffer); + }, + set: function (newValue) { + mono_wasm_send_dbg_command_with_parms(-1, prop.set.commandSet, prop.set.command, prop.set.buffer, prop.set.length, prop.set.valtype, newValue); return commands_received.res_ok; + } + } + ); + } else if (prop.set !== undefined) { + Object.defineProperty(proxy, + prop.name, + { + get() { + return prop.value; + }, + set: function (newValue) { + mono_wasm_send_dbg_command_with_parms(-1, prop.set.commandSet, prop.set.command, prop.set.buffer, prop.set.length, prop.set.valtype, newValue); return commands_received.res_ok; + } + } + ); + } else { + proxy[prop.name] = prop.value; + } + }); + return proxy; +} + +export function mono_wasm_call_function_on(request: CallRequest) { + if (request.arguments != undefined && !Array.isArray(request.arguments)) + throw new Error(`"arguments" should be an array, but was ${request.arguments}`); + + const objId = request.objectId; + const details = request.details; + let proxy; + + if (objId.startsWith('dotnet:cfo_res:')) { + if (objId in _call_function_res_cache) + proxy = _call_function_res_cache[objId]; + else + throw new Error(`Unknown object id ${objId}`); + } else { + proxy = _create_proxy_from_object_id(objId, details); + } + + const fn_args = request.arguments != undefined ? request.arguments.map(a => JSON.stringify(a.value)) : []; + const fn_eval_str = `var fn = ${request.functionDeclaration}; fn.call (proxy, ...[${fn_args}]);`; + + const local_eval = eval; // https://rollupjs.org/guide/en/#avoiding-eval + const fn_res = local_eval(fn_eval_str); + if (fn_res === undefined) + return { type: "undefined" }; + + if (Object(fn_res) !== fn_res) { + if (typeof (fn_res) == "object" && fn_res == null) + return { type: typeof (fn_res), subtype: `${fn_res}`, value: null }; + return { type: typeof (fn_res), description: `${fn_res}`, value: `${fn_res}` }; + } + + if (request.returnByValue && fn_res.subtype == undefined) + return { type: "object", value: fn_res }; + + if (Object.getPrototypeOf(fn_res) == Array.prototype) { + + const fn_res_id = _cache_call_function_res(fn_res); + + return { + type: "object", + subtype: "array", + className: "Array", + description: `Array(${fn_res.length})`, + objectId: fn_res_id + }; + } + if (fn_res.value !== undefined || fn_res.subtype !== undefined) { + return fn_res; + } + + if (fn_res == proxy) + return { type: "object", className: "Object", description: "Object", objectId: objId }; + const fn_res_id = _cache_call_function_res(fn_res); + return { type: "object", className: "Object", description: "Object", objectId: fn_res_id }; +} + + +function _get_cfo_res_details(objectId: string, args: any) { + if (!(objectId in _call_function_res_cache)) + throw new Error(`Could not find any object with id ${objectId}`); + + const real_obj = _call_function_res_cache[objectId]; + + const descriptors = Object.getOwnPropertyDescriptors(real_obj); + if (args.accessorPropertiesOnly) { + Object.keys(descriptors).forEach(k => { + if (descriptors[k].get === undefined) + Reflect.deleteProperty(descriptors, k); + }); + } + + let res_details: any[] = []; + Object.keys(descriptors).forEach(k => { + let new_obj; + let prop_desc = descriptors[k]; + if (typeof prop_desc.value == "object") { + // convert `{value: { type='object', ... }}` + // to `{ name: 'foo', value: { type='object', ... }} + new_obj = Object.assign({ name: k }, prop_desc); + } else if (prop_desc.value !== undefined) { + // This is needed for values that were not added by us, + // thus are like { value: 5 } + // instead of { value: { type = 'number', value: 5 }} + // + // This can happen, for eg., when `length` gets added for arrays + // or `__proto__`. + new_obj = { + name: k, + // merge/add `type` and `description` to `d.value` + value: Object.assign({ type: (typeof prop_desc.value), description: '' + prop_desc.value }, + prop_desc) + }; + } else if (prop_desc.get !== undefined) { + // The real_obj has the actual getter. We are just returning a placeholder + // If the caller tries to run function on the cfo_res object, + // that accesses this property, then it would be run on `real_obj`, + // which *has* the original getter + new_obj = { + name: k, + get: { + className: "Function", + description: `get ${k} () {}`, + type: "function" + } + }; + } else { + new_obj = { name: k, value: { type: "symbol", value: "", description: "" } }; + } + + res_details.push(new_obj); + }); + + return { __value_as_json_string__: JSON.stringify(res_details) }; +} + +export function mono_wasm_get_details(objectId: string, args = {}) { + return _get_cfo_res_details(`dotnet:cfo_res:${objectId}`, args); +} + +function _cache_call_function_res(obj: any) { + const id = `dotnet:cfo_res:${_next_call_function_res_id++}`; + _call_function_res_cache[id] = obj; + return id; +} + +export function mono_wasm_release_object(objectId: string) { + if (objectId in _call_function_res_cache) + delete _call_function_res_cache[objectId]; +} + +type CallDetails = { + value: string +} + +type CallArgs = { + value: string +} + +type CallRequest = { + arguments: undefined | Array, + objectId: string, + details: CallDetails[], + functionDeclaration: string + returnByValue: boolean, +} + +type WasmEvent = { + eventName: string, // - name of the event being raised + [i: string]: any, // - arguments for the event itself +} diff --git a/src/mono/wasm/runtime/src/mono/icu.ts b/src/mono/wasm/runtime/src/mono/icu.ts new file mode 100644 index 0000000000000..041ba2cc5908e --- /dev/null +++ b/src/mono/wasm/runtime/src/mono/icu.ts @@ -0,0 +1,60 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +import cwraps from './cwraps' + +let num_icu_assets_loaded_successfully = 0; + +// @offset must be the address of an ICU data archive in the native heap. +// returns true on success. +export function mono_wasm_load_icu_data(offset: VoidPtr): boolean { + var ok = (cwraps.mono_wasm_load_icu_data(offset)) === 1; + if (ok) + num_icu_assets_loaded_successfully++; + return ok; +} + +// Get icudt.dat exact filename that matches given culture, examples: +// "ja" -> "icudt_CJK.dat" +// "en_US" (or "en-US" or just "en") -> "icudt_EFIGS.dat" +// etc, see "mono_wasm_get_icudt_name" implementation in pal_icushim_static.c +export function mono_wasm_get_icudt_name(culture: string): string { + return cwraps.mono_wasm_get_icudt_name(culture); +} + + +// Performs setup for globalization. +// @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) { + var invariantMode = false; + + if (globalization_mode === "invariant") + invariantMode = true; + + if (!invariantMode) { + if (num_icu_assets_loaded_successfully > 0) { + 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"); + invariantMode = true; + } else { + var msg = "invariant globalization mode is inactive and no ICU data archives were loaded"; + console.error("MONO_WASM: ERROR: " + msg); + throw new Error(msg); + } + } + + if (invariantMode) + cwraps.mono_wasm_setenv("DOTNET_SYSTEM_GLOBALIZATION_INVARIANT", "1"); + + // Set globalization mode to PredefinedCulturesOnly + cwraps.mono_wasm_setenv("DOTNET_SYSTEM_GLOBALIZATION_PREDEFINED_CULTURES_ONLY", "1"); +} + +export const enum GlobalizationMode { + ICU = "icu", // load ICU globalization data from any runtime assets with behavior "icu". + INVARIANT = "invariant", // operate in invariant globalization mode. + AUTO = "auto" // (default): if "icu" behavior assets are present, use ICU, otherwise invariant. +} diff --git a/src/mono/wasm/runtime/src/mono/init.ts b/src/mono/wasm/runtime/src/mono/init.ts new file mode 100644 index 0000000000000..bcb9819c58e39 --- /dev/null +++ b/src/mono/wasm/runtime/src/mono/init.ts @@ -0,0 +1,490 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +import { Module, MONO } from '../runtime' +import { AssetEntry, MonoConfig } from './types' +import cwraps from './cwraps' +import { mono_wasm_runtime_ready } from './debug'; +import { mono_wasm_load_bytes_into_heap } from './roots' +import { mono_wasm_globalization_init, mono_wasm_load_icu_data } from './icu'; +import { toBase64StringImpl } from './base64'; +import { mono_wasm_init_aot_profiler, mono_wasm_init_coverage_profiler } from './profiler'; + +// 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) { + cwraps.mono_wasm_setenv(name, value); +} + +export function mono_wasm_set_runtime_options(options: string[]) { + var argv = Module._malloc(options.length * 4); + let aindex = 0; + for (var i = 0; i < options.length; ++i) { + Module.setValue(argv + (aindex * 4), cwraps.mono_wasm_strdup(options[i]), "i32"); + aindex += 1; + } + cwraps.mono_wasm_parse_runtime_options(options.length, argv); +} + +function _handle_loaded_asset(ctx: MonoInitContext, asset: AssetEntry, url: string, blob: ArrayBuffer) { + var bytes = new Uint8Array(blob); + if (ctx.tracing) + console.log(`MONO_WASM: Loaded:${asset.name} as ${asset.behavior} size ${bytes.length} from ${url}`); + + var virtualName: string = asset.virtual_path || asset.name; + var offset: VoidPtr | null = null; + + switch (asset.behavior) { + case "resource": + case "assembly": + ctx.loaded_files.push({ url: url, file: virtualName }); + case "heap": + case "icu": + offset = mono_wasm_load_bytes_into_heap(bytes); + ctx.loaded_assets[virtualName] = [offset, bytes.length]; + break; + + case "vfs": + // FIXME + var lastSlash = virtualName.lastIndexOf("/"); + var parentDirectory = (lastSlash > 0) + ? virtualName.substr(0, lastSlash) + : null; + var fileName = (lastSlash > 0) + ? virtualName.substr(lastSlash + 1) + : virtualName; + if (fileName.startsWith("/")) + fileName = fileName.substr(1); + if (parentDirectory) { + if (ctx.tracing) + console.log("MONO_WASM: Creating directory '" + parentDirectory + "'"); + + var pathRet = ctx.createPath( + "/", parentDirectory, true, true // fixme: should canWrite be false? + ); + } else { + parentDirectory = "/"; + } + + if (ctx.tracing) + console.log("MONO_WASM: Creating file '" + fileName + "' in directory '" + parentDirectory + "'"); + + if (!mono_wasm_load_data_archive(bytes, parentDirectory)) { + var fileRet = ctx.createDataFile( + parentDirectory, fileName, + bytes, true /* canRead */, true /* canWrite */, true /* canOwn */ + ); + } + break; + + default: + throw new Error(`Unrecognized asset behavior:${asset.behavior}, for asset ${asset.name}`); + } + + if (asset.behavior === "assembly") { + var hasPpdb = cwraps.mono_wasm_add_assembly(virtualName, offset!, bytes.length); + + if (!hasPpdb) { + var index = ctx.loaded_files.findIndex(element => element.file == virtualName); + ctx.loaded_files.splice(index, 1); + } + } + else if (asset.behavior === "icu") { + if (!mono_wasm_load_icu_data(offset!)) + console.error("Error loading ICU asset", asset.name); + } + else if (asset.behavior === "resource") { + cwraps.mono_wasm_add_satellite_assembly(virtualName, asset.culture!, offset!, bytes.length); + } +} + +// Initializes the runtime and loads assemblies, debug information, and other files. +// @args is a dictionary-style Object with the following properties: +// assembly_root: (required) the subfolder containing managed assemblies and pdbs +// debug_level or enable_debugging: (required) +// assets: (required) a list of assets to load along with the runtime. each asset +// is a dictionary-style Object with the following properties: +// name: (required) the name of the asset, including extension. +// behavior: (required) determines how the asset will be handled once loaded: +// "heap": store asset into the native heap +// "assembly": load asset as a managed assembly (or debugging information) +// "resource": load asset as a managed resource assembly +// "icu": load asset as an ICU data archive +// "vfs": load asset into the virtual filesystem (for fopen, File.Open, etc) +// load_remote: (optional) if true, an attempt will be made to load the asset +// from each location in @args.remote_sources. +// virtual_path: (optional) if specified, overrides the path of the asset in +// the virtual filesystem and similar data structures once loaded. +// is_optional: (optional) if true, any failure to load this asset will be ignored. +// loaded_cb: (required) a function () invoked when loading has completed. +// fetch_file_cb: (optional) a function (string) invoked to fetch a given file. +// If no callback is provided a default implementation appropriate for the current +// environment will be selected (readFileSync in node, fetch elsewhere). +// If no default implementation is available this call will fail. +// remote_sources: (optional) additional search locations for assets. +// sources will be checked in sequential order until the asset is found. +// the string "./" indicates to load from the application directory (as with the +// files in assembly_list), and a fully-qualified URL like "https://example.com/" indicates +// that asset loads can be attempted from a remote server. Sources must end with a "/". +// environment_variables: (optional) dictionary-style Object containing environment variables +// runtime_options: (optional) array of runtime options as strings +// aot_profiler_options: (optional) dictionary-style Object. see the comments for +// mono_wasm_init_aot_profiler. If omitted, aot profiler will not be initialized. +// coverage_profiler_options: (optional) dictionary-style Object. see the comments for +// mono_wasm_init_coverage_profiler. If omitted, coverage profiler will not be initialized. +// globalization_mode: (optional) configures the runtime's globalization mode: +// "icu": load ICU globalization data from any runtime assets with behavior "icu". +// "invariant": operate in invariant globalization mode. +// "auto" (default): if "icu" behavior assets are present, use ICU, otherwise invariant. +// diagnostic_tracing: (optional) enables diagnostic log messages during startup +export function mono_load_runtime_and_bcl_args(args: MonoConfig) { + try { + return _load_assets_and_runtime(args); + } catch (exc: any) { + console.error("error in mono_load_runtime_and_bcl_args:", exc.toString()); + throw exc; + } +} + +function _apply_configuration_from_args(args: MonoConfig) { + for (var k in (args.environment_variables || {})) + mono_wasm_setenv(k, args.environment_variables![k]); + + if (args.runtime_options) + mono_wasm_set_runtime_options(args.runtime_options); + + if (args.aot_profiler_options) + mono_wasm_init_aot_profiler(args.aot_profiler_options); + + if (args.coverage_profiler_options) + mono_wasm_init_coverage_profiler(args.coverage_profiler_options); +} + +function _get_fetch_file_cb_from_args(args: MonoConfig): (asset: string) => Promise { + if (typeof (args.fetch_file_cb) === "function") + return args.fetch_file_cb; + + if (ENVIRONMENT_IS_NODE) { + var fs = require('fs'); + return function (asset) { + console.debug("MONO_WASM: Loading... " + asset); + var binary = fs.readFileSync(asset); + var resolve_func2 = function (resolve: Function, reject: Function) { + resolve(new Uint8Array(binary)); + }; + + var resolve_func1 = function (resolve: Function, reject: Function) { + var response = { + ok: true, + url: asset, + arrayBuffer: function () { + return new Promise(resolve_func2); + } + }; + resolve(response); + }; + + return new Promise(resolve_func1); + }; + } else if (typeof (fetch) === "function") { + return function (asset) { + return fetch(asset, { credentials: 'same-origin' }); + }; + } else { + throw new Error("No fetch_file_cb was provided and this environment does not expose 'fetch'."); + } +} + +function _finalize_startup(args: MonoConfig, ctx: MonoInitContext) { + var loaded_files_with_debug_info: string[] = []; + ctx.loaded_files.forEach(value => loaded_files_with_debug_info.push(value.url)); + MONO.loaded_assets = ctx.loaded_assets; + MONO.loaded_files = loaded_files_with_debug_info; + 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)); + } + + var load_runtime = cwraps.mono_wasm_load_runtime; + + console.debug("MONO_WASM: Initializing mono runtime"); + + mono_wasm_globalization_init(args.globalization_mode!); + + if (ENVIRONMENT_IS_SHELL || ENVIRONMENT_IS_NODE) { + try { + load_runtime("unused", args.debug_level || 0); + } catch (ex: any) { + Module.print("MONO_WASM: load_runtime () failed: " + ex); + Module.print("MONO_WASM: Stacktrace: \n"); + Module.print(ex.stack); + + var wasm_exit = cwraps.mono_wasm_exit; + wasm_exit(1); + } + } else { + load_runtime("unused", args.debug_level || 0); + } + + let tz; + try { + tz = Intl.DateTimeFormat().resolvedOptions().timeZone; + } catch { } + mono_wasm_setenv("TZ", tz || "UTC"); + mono_wasm_runtime_ready(); + args.loaded_cb(); +} + +function _load_assets_and_runtime(args: MonoConfig) { + if (args.enable_debugging) + args.debug_level = args.enable_debugging; + if (args.assembly_list) + throw new Error("Invalid args (assembly_list was replaced by assets)"); + if (args.runtime_assets) + throw new Error("Invalid args (runtime_assets was replaced by assets)"); + if (args.runtime_asset_sources) + throw new Error("Invalid args (runtime_asset_sources was replaced by remote_sources)"); + if (!args.loaded_cb) + throw new Error("loaded_cb not provided"); + + var ctx: MonoInitContext = { + tracing: args.diagnostic_tracing || false, + pending_count: args.assets.length, + loaded_assets: Object.create(null), + // dlls and pdbs, used by blazor and the debugger + loaded_files: [], + createPath: Module.FS_createPath, + createDataFile: Module.FS_createDataFile + }; + + if (ctx.tracing) + console.log("mono_wasm_load_runtime_with_args", JSON.stringify(args)); + + _apply_configuration_from_args(args); + + var fetch_file_cb = _get_fetch_file_cb_from_args(args); + + var onPendingRequestComplete = function () { + --ctx.pending_count; + + if (ctx.pending_count === 0) { + try { + _finalize_startup(args, ctx); + } catch (exc) { + console.error("Unhandled exception in _finalize_startup", exc); + throw exc; + } + } + }; + + var processFetchResponseBuffer = function (asset: AssetEntry, url: string, buffer: ArrayBuffer) { + try { + _handle_loaded_asset(ctx, asset, url, buffer); + } catch (exc) { + console.error(`Unhandled exception in processFetchResponseBuffer ${url}`, exc); + throw exc; + } finally { + onPendingRequestComplete(); + } + }; + + args.assets.forEach(function (asset: AssetEntry) { + var sourceIndex = 0; + var sourcesList = asset.load_remote ? args.remote_sources! : [""]; + + var handleFetchResponse = function (response: Response) { + if (!response.ok) { + try { + attemptNextSource(); + return; + } catch (exc) { + console.error("MONO_WASM: Unhandled exception in handleFetchResponse attemptNextSource for asset", asset.name, exc); + throw exc; + } + } + + try { + var bufferPromise = response.arrayBuffer(); + bufferPromise.then((data) => processFetchResponseBuffer(asset, response.url, data)); + } catch (exc) { + console.error("MONO_WASM: Unhandled exception in handleFetchResponse for asset", asset.name, exc); + attemptNextSource(); + } + }; + + const attemptNextSource = function () { + if (sourceIndex >= sourcesList.length) { + var msg = "MONO_WASM: Failed to load " + asset.name; + try { + var isOk = asset.is_optional || + (asset.name.match(/\.pdb$/) && args.ignore_pdb_load_errors); + + if (isOk) + console.debug(msg); + else { + console.error(msg); + throw new Error(msg); + } + } finally { + onPendingRequestComplete(); + } + } + + var sourcePrefix = sourcesList[sourceIndex]; + sourceIndex++; + + // HACK: Special-case because MSBuild doesn't allow "" as an attribute + if (sourcePrefix === "./") + sourcePrefix = ""; + + var attemptUrl; + if (sourcePrefix.trim() === "") { + if (asset.behavior === "assembly") + attemptUrl = locateFile(args.assembly_root + "/" + asset.name); + else if (asset.behavior === "resource") { + var path = asset.culture !== '' ? `${asset.culture}/${asset.name}` : asset.name; + attemptUrl = locateFile(args.assembly_root + "/" + path); + } + else + attemptUrl = asset.name; + } else { + attemptUrl = sourcePrefix + asset.name; + } + + try { + if (asset.name === attemptUrl) { + if (ctx.tracing) + console.log("Attempting to fetch '" + attemptUrl + "'"); + } else { + if (ctx.tracing) + console.log("Attempting to fetch '" + attemptUrl + "' for", asset.name); + } + var fetch_promise = fetch_file_cb(attemptUrl); + fetch_promise.then(handleFetchResponse); + } catch (exc) { + console.error("MONO_WASM: Error fetching " + attemptUrl, exc); + attemptNextSource(); + } + }; + + attemptNextSource(); + }); +} + +// used from ASP.NET +export function mono_wasm_load_data_archive(data: TypedArray, prefix: string) { + if (data.length < 8) + return false; + + var dataview = new DataView(data.buffer); + var magic = dataview.getUint32(0, true); + // get magic number + if (magic != 0x626c6174) { + return false; + } + var manifestSize = dataview.getUint32(4, true); + if (manifestSize == 0 || data.length < manifestSize + 8) + return false; + + var manifest; + try { + var manifestContent = Module.UTF8ArrayToString(data, 8, manifestSize); + manifest = JSON.parse(manifestContent); + if (!(manifest instanceof Array)) + return false; + } catch (exc) { + return false; + } + + data = data.slice(manifestSize + 8); + + // Create the folder structure + // /usr/share/zoneinfo + // /usr/share/zoneinfo/Africa + // /usr/share/zoneinfo/Asia + // .. + + var folders = new Set() + manifest.filter(m => { + var file = m[0]; + var last = file.lastIndexOf("/"); + var directory = file.slice(0, last + 1); + folders.add(directory); + }); + folders.forEach(folder => { + Module['FS_createPath'](prefix, folder, true, true); + }); + + for (var row of manifest) { + var name = row[0]; + var length = row[1]; + var bytes = data.slice(0, length); + Module['FS_createDataFile'](prefix, name, bytes, true, true); + data = data.slice(length); + } + return true; +} + +/** + * 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 + */ +export async function mono_wasm_load_config(configFilePath: string) { + Module.addRunDependency(configFilePath); + try { + let config = null; + // NOTE: when we add nodejs make sure to include the nodejs fetch package + if (ENVIRONMENT_IS_WEB) { + const configRaw = await fetch(configFilePath); + config = await configRaw.json(); + } else if (ENVIRONMENT_IS_NODE) { + config = require(configFilePath); + } else { // shell or worker + config = JSON.parse(read(configFilePath)); // read is a v8 debugger command + } + MONO.config = config; + Module.config = MONO.config; + } catch (e) { + const errMessage = "failed to load config file " + configFilePath; + console.error(errMessage) + MONO.config = { message: errMessage, error: e }; + Module.config = MONO.config; + } finally { + Module.removeRunDependency(configFilePath); + } +} + +export function mono_wasm_asm_loaded(assembly_name: number, assembly_ptr: number, assembly_len: number, pdb_ptr: number, pdb_len: number) { + // Only trigger this codepath for assemblies loaded after app is ready + if (MONO.mono_wasm_runtime_is_ready !== true) + return; + + const assembly_name_str = assembly_name !== 0 ? Module.UTF8ToString(assembly_name).concat('.dll') : ''; + const assembly_data = new Uint8Array(Module.HEAPU8.buffer, assembly_ptr, assembly_len); + const assembly_b64 = toBase64StringImpl(assembly_data); + + let pdb_b64; + if (pdb_ptr) { + const pdb_data = new Uint8Array(Module.HEAPU8.buffer, pdb_ptr, pdb_len); + pdb_b64 = toBase64StringImpl(pdb_data); + } + + MONO.mono_wasm_raise_debug_event({ + eventName: 'AssemblyLoaded', + assembly_name: assembly_name_str, + assembly_b64, + pdb_b64 + }); +} + +type MonoInitContext = { + tracing: boolean, + pending_count: number, + loaded_files: { url: string, file: string }[], + loaded_assets: { [id: string]: [VoidPtr, number] }, + createPath: Function, + createDataFile: Function +} \ No newline at end of file diff --git a/src/mono/wasm/runtime/src/mono/profiler.ts b/src/mono/wasm/runtime/src/mono/profiler.ts new file mode 100644 index 0000000000000..270ef69061345 --- /dev/null +++ b/src/mono/wasm/runtime/src/mono/profiler.ts @@ -0,0 +1,48 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +import { Module } from '../runtime' + +// Initialize the AOT profiler with OPTIONS. +// Requires the AOT profiler to be linked into the app. +// options = { write_at: "", send_to: "" } +// should be in the format ::. +// write_at defaults to 'WebAssembly.Runtime::StopProfile'. +// send_to defaults to 'WebAssembly.Runtime::DumpAotProfileData'. +// DumpAotProfileData stores the data into Module.aot_profile_data. +// +export function mono_wasm_init_aot_profiler(options: AOTProfilerOptions) { + if (options == null) + options = {} + if (!('write_at' in options)) + options.write_at = 'Interop/Runtime::StopProfile'; + if (!('send_to' in options)) + options.send_to = 'Interop/Runtime::DumpAotProfileData'; + var arg = "aot:write-at-method=" + options.write_at + ",send-to-method=" + options.send_to; + Module.ccall('mono_wasm_load_profiler_aot', null, ['string'], [arg]); +} + +// options = { write_at: "", send_to: "" } +// should be in the format ::. +// write_at defaults to 'WebAssembly.Runtime::StopProfile'. +// send_to defaults to 'WebAssembly.Runtime::DumpCoverageProfileData'. +// DumpCoverageProfileData stores the data into Module.coverage_profile_data. +export function mono_wasm_init_coverage_profiler(options: CoverageProfilerOptions) { + if (options == null) + options = {} + if (!('write_at' in options)) + options.write_at = 'WebAssembly.Runtime::StopProfile'; + if (!('send_to' in options)) + options.send_to = 'WebAssembly.Runtime::DumpCoverageProfileData'; + var arg = "coverage:write-at-method=" + options.write_at + ",send-to-method=" + options.send_to; + Module.ccall('mono_wasm_load_profiler_coverage', null, ['string'], [arg]); +} + +export type AOTProfilerOptions = { + write_at?: string, // should be in the format ::, default: 'WebAssembly.Runtime::StopProfile' + send_to?: string // should be in the format ::, default: 'WebAssembly.Runtime::DumpAotProfileData' (DumpAotProfileData stores the data into Module.aot_profile_data.) +} +export type CoverageProfilerOptions = { + write_at?: string, // should be in the format ::, default: 'WebAssembly.Runtime::StopProfile' + send_to?: string // should be in the format ::, default: 'WebAssembly.Runtime::DumpCoverageProfileData' (DumpCoverageProfileData stores the data into Module.coverage_profile_data.) +} \ No newline at end of file diff --git a/src/mono/wasm/runtime/src/mono/roots.ts b/src/mono/wasm/runtime/src/mono/roots.ts new file mode 100644 index 0000000000000..220a6e4927f40 --- /dev/null +++ b/src/mono/wasm/runtime/src/mono/roots.ts @@ -0,0 +1,304 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +import cwraps from './cwraps' +import { Module } from '../runtime' + +const maxScratchRoots = 8192; +let _scratch_root_buffer: WasmRootBuffer | null = null; +let _scratch_root_free_indices: Int32Array | null = null; +let _scratch_root_free_indices_count = 0; +const _scratch_root_free_instances: WasmRoot[] = []; + +// @bytes must be a typed array. space is allocated for it in the native heap +// and it is copied to that location. returns the address of the allocation. +export function mono_wasm_load_bytes_into_heap(bytes: Uint8Array): VoidPtr { + var memoryOffset = Module._malloc(bytes.length); + var heapBytes = new Uint8Array(Module.HEAPU8.buffer, memoryOffset, bytes.length); + heapBytes.set(bytes); + return memoryOffset; +} + +/** + * 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. + */ +export function mono_wasm_new_root_buffer(capacity: number, msg: string): WasmRootBuffer { + if (capacity <= 0) + throw new Error("capacity >= 1"); + + capacity = capacity | 0; + + var capacityBytes = capacity * 4; + var offset = Module._malloc(capacityBytes); + if ((offset % 4) !== 0) + throw new Error("Malloc returned an unaligned offset"); + + _zero_region(offset, capacityBytes); + + return new WasmRootBuffer(offset, capacity, true, msg); +} + +/** + * Creates a root buffer object representing an existing allocation in the native heap and registers + * the allocation with the GC. The caller is responsible for managing the lifetime of the allocation. + */ +export function mono_wasm_new_root_buffer_from_pointer(offset: number, capacity: number, msg: string): WasmRootBuffer { + if (capacity <= 0) + throw new Error("capacity >= 1"); + + capacity = capacity | 0; + + var capacityBytes = capacity * 4; + if ((offset % 4) !== 0) + throw new Error("Unaligned offset"); + + _zero_region(offset, capacityBytes); + + return new WasmRootBuffer(offset, capacity, false, msg); +} + +/** + * 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. + */ +export function mono_wasm_new_root(value: ManagedPointer | undefined = undefined): WasmRoot { + var result: WasmRoot; + + if (_scratch_root_free_instances.length > 0) { + result = _scratch_root_free_instances.pop()!; + } else { + var index = _mono_wasm_claim_scratch_index(); + var buffer = _scratch_root_buffer; + + result = new WasmRoot(buffer!, index); + } + + if (value !== undefined) { + if (typeof (value) !== "number") + throw new Error("value must be an address in the managed heap"); + + result.set(value); + } else { + result.set(0); + } + + return result; +} + +/** + * Allocates 1 or more temporary roots, accepting either a number of roots or an array of pointers. + * mono_wasm_new_roots(n): returns an array of N zero-initialized roots. + * mono_wasm_new_roots([a, b, ...]) returns an array of new roots initialized with each element. + * Each root must be released with its release method, or using the mono_wasm_release_roots API. + */ +export function mono_wasm_new_roots(count_or_values: number | ManagedPointer[]): WasmRoot[] { + var result; + + if (Array.isArray(count_or_values)) { + result = new Array(count_or_values.length); + for (var i = 0; i < result.length; i++) + result[i] = mono_wasm_new_root(count_or_values[i]); + } else if ((count_or_values | 0) > 0) { + result = new Array(count_or_values); + for (var i = 0; i < result.length; i++) + result[i] = mono_wasm_new_root(); + } else { + throw new Error("count_or_values must be either an array or a number greater than 0"); + } + + return result; +} + +/** + * 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 + */ +export function mono_wasm_release_roots() { + for (var i = 0; i < arguments.length; i++) { + if (!arguments[i]) + continue; + + arguments[i].release(); + } +} + +function _zero_region(byteOffset: number, sizeBytes: number) { + if (((byteOffset % 4) === 0) && ((sizeBytes % 4) === 0)) + Module.HEAP32.fill(0, byteOffset / 4, sizeBytes / 4); + else + Module.HEAP8.fill(0, byteOffset, sizeBytes); +} + +function _mono_wasm_release_scratch_index(index: number) { + if (index === undefined) + return; + + _scratch_root_buffer!.set(index, 0); + _scratch_root_free_indices![_scratch_root_free_indices_count] = index; + _scratch_root_free_indices_count++; +} + +function _mono_wasm_claim_scratch_index() { + if (!_scratch_root_buffer || !_scratch_root_free_indices) { + _scratch_root_buffer = mono_wasm_new_root_buffer(maxScratchRoots, "js roots"); + + _scratch_root_free_indices = new Int32Array(maxScratchRoots); + _scratch_root_free_indices_count = maxScratchRoots; + for (var i = 0; i < maxScratchRoots; i++) + _scratch_root_free_indices[i] = maxScratchRoots - i - 1; + } + + if (_scratch_root_free_indices_count < 1) + throw new Error("Out of scratch root space"); + + var result = _scratch_root_free_indices[_scratch_root_free_indices_count - 1]; + _scratch_root_free_indices_count--; + return result; +} + + +export class WasmRootBuffer { + private __count: number; + private length: number; + private __offset: number; + private __offset32: number; + private __handle: number; + private __ownsAllocation: boolean; + + constructor(offset: number, capacity: number, ownsAllocation: boolean, msg: string) { + const capacityBytes = capacity * 4; + + this.__offset = offset; + this.__offset32 = (offset / 4) | 0; + this.__count = capacity; + this.length = capacity; + this.__handle = cwraps.mono_wasm_register_root(offset, capacityBytes, msg || 0); + this.__ownsAllocation = ownsAllocation; + } + + _throw_index_out_of_range() { + throw new Error("index out of range"); + } + + _check_in_range(index: number) { + if ((index >= this.__count) || (index < 0)) + this._throw_index_out_of_range(); + } + + get_address(index: number): NativePointer { + this._check_in_range(index); + return this.__offset + (index * 4); + } + + get_address_32(index: number) { + this._check_in_range(index); + return this.__offset32 + index; + } + + get(index: number): ManagedPointer { + this._check_in_range(index); + return Module.HEAP32[this.get_address_32(index)]; + } + + set(index: number, value: ManagedPointer) { + Module.HEAP32[this.get_address_32(index)] = value; + return value; + } + + _unsafe_get(index: number) { + return Module.HEAP32[this.__offset32 + index]; + } + + _unsafe_set(index: number, value: ManagedPointer) { + Module.HEAP32[this.__offset32 + index] = value; + } + + clear() { + if (this.__offset) + _zero_region(this.__offset, this.__count * 4); + } + + release() { + if (this.__offset && this.__ownsAllocation) { + cwraps.mono_wasm_deregister_root(this.__offset); + _zero_region(this.__offset, this.__count * 4); + Module._free(this.__offset); + } + + this.__handle = this.__offset = this.__count = this.__offset32 = 0; + } + + toString() { + return "[root buffer @" + this.get_address(0) + ", size " + this.__count + "]"; + } +} + +export class WasmRoot { + private __buffer: WasmRootBuffer; + private __index: number; + + constructor(buffer: WasmRootBuffer, index: number) { + this.__buffer = buffer;//TODO + this.__index = index; + } + + get_address(): NativePointer { + return this.__buffer.get_address(this.__index); + } + + get_address_32(): number { + return this.__buffer.get_address_32(this.__index); + } + + get(): ManagedPointer { + var result = this.__buffer._unsafe_get(this.__index); + return result; + } + + set(value: ManagedPointer) { + this.__buffer._unsafe_set(this.__index, value); + return value; + } + + get value(): ManagedPointer { + return this.get(); + } + + set value(value: ManagedPointer) { + this.set(value); + } + + /** @returns {ManagedPointer} */ + valueOf(): ManagedPointer { + return this.get(); + } + + clear() { + this.set(0); + } + + release() { + const maxPooledInstances = 128; + if (_scratch_root_free_instances.length > maxPooledInstances) { + _mono_wasm_release_scratch_index(this.__index); + (this).__buffer = null; + this.__index = 0; + } else { + this.set(0); + _scratch_root_free_instances.push(this); + } + } + + toString() { + return "[root @" + this.get_address() + "]"; + } +} diff --git a/src/mono/wasm/runtime/src/mono/scheduling.ts b/src/mono/wasm/runtime/src/mono/scheduling.ts new file mode 100644 index 0000000000000..060fc9df99ff5 --- /dev/null +++ b/src/mono/wasm/runtime/src/mono/scheduling.ts @@ -0,0 +1,78 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +import cwraps from './cwraps' + +const timeout_queue: Function[] = []; +let spread_timers_maximum = 0; +let isChromium = false; +let pump_count = 0; + +if (globalThis.navigator) { + const nav: any = globalThis.navigator; + if (nav.userAgentData && nav.userAgentData.brands) { + isChromium = nav.userAgentData.brands.some((i: any) => i.brand == 'Chromium'); + } + else if (nav.userAgent) { + isChromium = nav.userAgent.includes("Chrome"); + } +} + +function pump_message() { + while (timeout_queue.length > 0) { + --pump_count; + const cb: Function = timeout_queue.shift()!; + cb(); + } + while (pump_count > 0) { + --pump_count; + cwraps.mono_background_exec(); + } +} + +function mono_wasm_set_timeout_exec(id: number) { + cwraps.mono_set_timeout_exec(id); +} + +export function prevent_timer_throttling() { + if (isChromium) { + return; + } + + // this will schedule timers every second for next 6 minutes, it should be called from WebSocket event, to make it work + // on next call, it would only extend the timers to cover yet uncovered future + let now = new Date().valueOf(); + const desired_reach_time = now + (1000 * 60 * 6); + const next_reach_time = Math.max(now + 1000, spread_timers_maximum); + const light_throttling_frequency = 1000; + for (var schedule = next_reach_time; schedule < desired_reach_time; schedule += light_throttling_frequency) { + const delay = schedule - now; + setTimeout(() => { + mono_wasm_set_timeout_exec(0); + pump_count++; + pump_message(); + }, delay); + } + spread_timers_maximum = desired_reach_time; +} + +export function schedule_background_exec() { + ++pump_count; + if (typeof globalThis.setTimeout === 'function') { + globalThis.setTimeout(pump_message, 0); + } +} + +export function mono_set_timeout(timeout: number, id: number) { + + if (typeof globalThis.setTimeout === 'function') { + globalThis.setTimeout(function () { + mono_wasm_set_timeout_exec(id); + }, timeout); + } else { + ++pump_count; + timeout_queue.push(function () { + mono_wasm_set_timeout_exec(id); + }) + } +} \ No newline at end of file diff --git a/src/mono/wasm/runtime/src/mono/string-decoder.ts b/src/mono/wasm/runtime/src/mono/string-decoder.ts new file mode 100644 index 0000000000000..cca7fc8f15472 --- /dev/null +++ b/src/mono/wasm/runtime/src/mono/string-decoder.ts @@ -0,0 +1,81 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +import { mono_wasm_new_root } from './roots' +import cwraps from './cwraps' +import { Module } from '../runtime' + +const mono_wasm_empty_string = ""; + +export class StringDecoder { + + private mono_wasm_string_root: any; + private mono_text_decoder: TextDecoder | undefined | null; + private mono_wasm_string_decoder_buffer: number | undefined; + private interned_string_table: Map | undefined; + + copy(mono_string: MonoString) { + if (!this.interned_string_table || !this.mono_wasm_string_decoder_buffer) { + this.mono_text_decoder = typeof TextDecoder !== 'undefined' ? new TextDecoder('utf-16le') : null; + this.mono_wasm_string_root = mono_wasm_new_root(); + this.mono_wasm_string_decoder_buffer = Module._malloc(12); + this.interned_string_table = new Map(); + } + if (mono_string === 0) + return null; + + this.mono_wasm_string_root.value = mono_string; + + let ppChars = this.mono_wasm_string_decoder_buffer + 0, + pLengthBytes = this.mono_wasm_string_decoder_buffer + 4, + pIsInterned = this.mono_wasm_string_decoder_buffer + 8; + + cwraps.mono_wasm_string_get_data(mono_string, ppChars, pLengthBytes, pIsInterned); + + let result = mono_wasm_empty_string; + let lengthBytes = Module.HEAP32[pLengthBytes / 4], + pChars = Module.HEAP32[ppChars / 4], + isInterned = Module.HEAP32[pIsInterned / 4]; + + if (pLengthBytes && pChars) { + if ( + isInterned && + this.interned_string_table && + this.interned_string_table.has(mono_string) //TODO remove 2x lookup + ) { + result = this.interned_string_table.get(mono_string)!; + // console.log("intern table cache hit", mono_string, result.length); + } else { + result = this.decode(pChars, pChars + lengthBytes); + if (isInterned) { + // console.log("interned", mono_string, result.length); + this.interned_string_table.set(mono_string, result); + } + } + } + + this.mono_wasm_string_root.value = 0; + return result; + } + + private decode(start: number, end: number) { + var str = ""; + if (this.mono_text_decoder) { + // When threading is enabled, TextDecoder does not accept a view of a + // SharedArrayBuffer, we must make a copy of the array first. + // See https://github.com/whatwg/encoding/issues/172 + var subArray = typeof SharedArrayBuffer !== 'undefined' && Module.HEAPU8.buffer instanceof SharedArrayBuffer + ? Module.HEAPU8.slice(start, end) + : Module.HEAPU8.subarray(start, end); + + str = this.mono_text_decoder.decode(subArray); + } else { + for (var i = 0; i < end - start; i += 2) { + var char = Module.getValue(start + i, 'i16'); + str += String.fromCharCode(char); + } + } + + return str; + } +} diff --git a/src/mono/wasm/runtime/src/mono/types.ts b/src/mono/wasm/runtime/src/mono/types.ts new file mode 100644 index 0000000000000..597850e14b8f2 --- /dev/null +++ b/src/mono/wasm/runtime/src/mono/types.ts @@ -0,0 +1,157 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +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 +} from './roots' +import { + mono_wasm_add_dbg_command_received, + mono_wasm_send_dbg_command_with_parms, + mono_wasm_send_dbg_command, + mono_wasm_get_dbg_command_info, + mono_wasm_get_details, + mono_wasm_release_object, + mono_wasm_call_function_on, + mono_wasm_debugger_resume, + mono_wasm_detach_debugger, + mono_wasm_runtime_ready, + mono_wasm_get_loaded_files, + mono_wasm_raise_debug_event, + mono_wasm_fire_debugger_agent_message +} from './debug' +import { StringDecoder } from './string-decoder' +import { mono_wasm_load_bytes_into_heap } from './roots' +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 +} from './init' +import { prevent_timer_throttling, mono_set_timeout, schedule_background_exec } from './scheduling' +import { mono_wasm_load_icu_data, mono_wasm_get_icudt_name, GlobalizationMode } from './icu' +import { AOTProfilerOptions, CoverageProfilerOptions } from './profiler' + +// this represents visibility for the C code +export interface t_MonoSupportLib { + mono_set_timeout: typeof mono_set_timeout + mono_wasm_asm_loaded: typeof mono_wasm_asm_loaded + mono_wasm_fire_debugger_agent_message: typeof mono_wasm_fire_debugger_agent_message, + schedule_background_exec: typeof schedule_background_exec + mono_wasm_setenv: typeof mono_wasm_setenv + mono_wasm_new_root_buffer: typeof mono_wasm_new_root_buffer; + mono_wasm_new_root_buffer_from_pointer: typeof mono_wasm_new_root_buffer_from_pointer; + mono_wasm_new_root: typeof mono_wasm_new_root; + mono_wasm_new_roots: typeof mono_wasm_new_roots; + mono_wasm_release_roots: typeof mono_wasm_release_roots; + mono_wasm_load_bytes_into_heap: typeof mono_wasm_load_bytes_into_heap; + mono_wasm_get_loaded_files: typeof mono_wasm_get_loaded_files; + mono_wasm_load_icu_data: typeof mono_wasm_load_icu_data; + mono_wasm_get_icudt_name: typeof mono_wasm_get_icudt_name; +} + +// this represents visibility in the javascript +// TODO limit this only to public API methods +export interface t_MONO { + loaded_files: string[]; + loaded_assets: { [id: string]: [VoidPtr, number] }, + mono_wasm_runtime_is_ready: boolean; + config?: MonoConfig, + + string_decoder: StringDecoder, + mono_set_timeout: typeof mono_set_timeout + mono_wasm_asm_loaded: typeof mono_wasm_asm_loaded + mono_wasm_fire_debugger_agent_message: typeof mono_wasm_fire_debugger_agent_message, + schedule_background_exec: typeof schedule_background_exec + mono_wasm_new_root: typeof mono_wasm_new_root, + mono_wasm_new_roots: typeof mono_wasm_new_roots, + mono_wasm_release_roots: typeof mono_wasm_release_roots, + mono_wasm_new_root_buffer: typeof mono_wasm_new_root_buffer, + mono_wasm_new_root_buffer_from_pointer: typeof mono_wasm_new_root_buffer_from_pointer + mono_wasm_load_bytes_into_heap: typeof mono_wasm_load_bytes_into_heap + mono_wasm_add_dbg_command_received: typeof mono_wasm_add_dbg_command_received + mono_wasm_send_dbg_command_with_parms: typeof mono_wasm_send_dbg_command_with_parms + mono_wasm_send_dbg_command: typeof mono_wasm_send_dbg_command + mono_wasm_get_dbg_command_info: typeof mono_wasm_get_dbg_command_info + mono_wasm_get_details: typeof mono_wasm_get_details + mono_wasm_release_object: typeof mono_wasm_release_object + mono_wasm_call_function_on: typeof mono_wasm_call_function_on + mono_wasm_debugger_resume: typeof mono_wasm_debugger_resume + mono_wasm_detach_debugger: typeof mono_wasm_detach_debugger + mono_wasm_runtime_ready: typeof mono_wasm_runtime_ready + mono_wasm_get_loaded_files: typeof mono_wasm_get_loaded_files + mono_wasm_raise_debug_event: typeof mono_wasm_raise_debug_event + mono_load_runtime_and_bcl_args: typeof mono_load_runtime_and_bcl_args + prevent_timer_throttling: typeof prevent_timer_throttling + mono_wasm_load_icu_data: typeof mono_wasm_load_icu_data + mono_wasm_get_icudt_name: typeof mono_wasm_get_icudt_name + mono_wasm_load_config: typeof mono_wasm_load_config + mono_wasm_setenv: typeof mono_wasm_setenv + mono_wasm_set_runtime_options: typeof mono_wasm_set_runtime_options + mono_wasm_load_data_archive: typeof mono_wasm_load_data_archive +} + +// how we extended wasm Module +export interface t_ModuleExtension { + config?: MonoConfig, +} + +export type MonoConfig = { + assembly_root: string, // the subfolder containing managed assemblies and pdbs + assets: (AssetEntry | AssetEntry | SatelliteAssemblyEntry | VfsEntry | IcuData)[], // a list of assets to load along with the runtime. each asset is a dictionary-style Object with the following properties: + loaded_cb: Function, // a function invoked when loading has completed + debug_level?: number, // Either this or the next one needs to be set + enable_debugging?: number, // Either this or the previous one needs to be set + fetch_file_cb?: Request, // a function (string) invoked to fetch a given file. If no callback is provided a default implementation appropriate for the current environment will be selected (readFileSync in node, fetch elsewhere). If no default implementation is available this call will fail. + globalization_mode: GlobalizationMode, // configures the runtime's globalization mode + assembly_list?: any, // obsolete but necessary for the check + runtime_assets?: any, // obsolete but necessary for the check + runtime_asset_sources?: any, // obsolete but necessary for the check + diagnostic_tracing?: boolean // enables diagnostic log messages during startup + remote_sources?: string[], // additional search locations for assets. Sources will be checked in sequential order until the asset is found. The string "./" indicates to load from the application directory (as with the files in assembly_list), and a fully-qualified URL like "https://example.com/" indicates that asset loads can be attempted from a remote server. Sources must end with a "/". + environment_variables?: { + [i: string]: string; + }, // dictionary-style Object containing environment variables + runtime_options?: string[], // array of runtime options as strings + aot_profiler_options?: AOTProfilerOptions, // dictionary-style Object. If omitted, aot profiler will not be initialized. + coverage_profiler_options?: CoverageProfilerOptions, // dictionary-style Object. If omitted, coverage profiler will not be initialized. + ignore_pdb_load_errors?: boolean +}; + +export type MonoConfigError = { message: string, error: any } + +// Types of assets that can be in the mono-config.js/mono-config.json file (taken from /src/tasks/WasmAppBuilder/WasmAppBuilder.cs) +export type AssetEntry = { + name: string, // the name of the asset, including extension. + behavior: AssetBehaviours, // determines how the asset will be handled once loaded + virtual_path?: string, // if specified, overrides the path of the asset in the virtual filesystem and similar data structures once loaded. + culture?: string, + load_remote?: boolean, // if true, an attempt will be made to load the asset from each location in @args.remote_sources. + is_optional?: boolean // if true, any failure to load this asset will be ignored. +} + +export interface AssemblyEntry extends AssetEntry { + name: "assembly" +} + +export interface SatelliteAssemblyEntry extends AssetEntry { + name: "resource", + culture: string +} + +export interface VfsEntry extends AssetEntry { + name: "vfs", + virtual_path: string +} + +export interface IcuData extends AssetEntry { + name: "icu", + load_remote: boolean +} + +// Note that since these are annoated as `declare const enum` they are replaces by tsc with their raw value during compilation +export const enum AssetBehaviours { + Resource = "resource", // load asset as a managed resource assembly + Assembly = "assembly", // load asset as a managed assembly (or debugging information) + Heap = "heap", // store asset into the native heap + ICU = "icu", // load asset as an ICU data archive + VFS = "vfs", // load asset into the virtual filesystem (for fopen, File.Open, etc) +} diff --git a/src/mono/wasm/runtime/src/runtime.ts b/src/mono/wasm/runtime/src/runtime.ts new file mode 100644 index 0000000000000..915a380e8f64f --- /dev/null +++ b/src/mono/wasm/runtime/src/runtime.ts @@ -0,0 +1,14 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +/// +/// +import { t_MONO, t_ModuleExtension } from './mono/types' + +export var Module: t_Module & t_ModuleExtension; +export var MONO: t_MONO; + +export function setMONO(mono: t_MONO, module: t_Module & any) { + Module = module; + MONO = mono; +} \ No newline at end of file diff --git a/src/mono/wasm/runtime/src/startup.ts b/src/mono/wasm/runtime/src/startup.ts new file mode 100644 index 0000000000000..134b5d757675a --- /dev/null +++ b/src/mono/wasm/runtime/src/startup.ts @@ -0,0 +1,70 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +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 +} from './mono/roots' +import { + mono_wasm_add_dbg_command_received, + mono_wasm_send_dbg_command_with_parms, + mono_wasm_send_dbg_command, + mono_wasm_get_dbg_command_info, + mono_wasm_get_details, + mono_wasm_release_object, + mono_wasm_call_function_on, + mono_wasm_debugger_resume, + mono_wasm_detach_debugger, + mono_wasm_runtime_ready, + mono_wasm_get_loaded_files, + mono_wasm_raise_debug_event, + mono_wasm_fire_debugger_agent_message, +} from './mono/debug' +import { mono_wasm_load_bytes_into_heap } from './mono/roots' +import { StringDecoder } from './mono/string-decoder' +import { MONO, setMONO } from './runtime' +import { t_MONO, t_MonoSupportLib } from './mono/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 +} from './mono/init' +import { prevent_timer_throttling, mono_set_timeout, schedule_background_exec } from './mono/scheduling' +import { mono_wasm_load_icu_data, mono_wasm_get_icudt_name } from './mono/icu' + +export function export_functions(mono: t_MONO, module: t_Module & t_MonoSupportLib) { + setMONO(mono, module) + MONO.string_decoder = new StringDecoder(); + MONO.mono_wasm_fire_debugger_agent_message = module.mono_wasm_fire_debugger_agent_message = mono_wasm_fire_debugger_agent_message; + MONO.mono_set_timeout = module.mono_set_timeout = mono_set_timeout; + MONO.mono_wasm_asm_loaded = module.mono_wasm_asm_loaded = mono_wasm_asm_loaded; + MONO.schedule_background_exec = module.schedule_background_exec = schedule_background_exec; + + MONO.mono_wasm_setenv = module.mono_wasm_setenv = mono_wasm_setenv; + MONO.mono_wasm_new_root_buffer = module.mono_wasm_new_root_buffer = mono_wasm_new_root_buffer; + MONO.mono_wasm_new_root_buffer_from_pointer = module.mono_wasm_new_root_buffer_from_pointer = mono_wasm_new_root_buffer_from_pointer; + MONO.mono_wasm_new_root = module.mono_wasm_new_root = mono_wasm_new_root; + MONO.mono_wasm_new_roots = module.mono_wasm_new_roots = mono_wasm_new_roots; + MONO.mono_wasm_release_roots = module.mono_wasm_release_roots = mono_wasm_release_roots; + MONO.mono_wasm_load_bytes_into_heap = module.mono_wasm_load_bytes_into_heap = mono_wasm_load_bytes_into_heap; + MONO.mono_wasm_get_loaded_files = module.mono_wasm_get_loaded_files = mono_wasm_get_loaded_files; + MONO.mono_wasm_load_icu_data = module.mono_wasm_load_icu_data = mono_wasm_load_icu_data; + MONO.mono_wasm_get_icudt_name = module.mono_wasm_get_icudt_name = mono_wasm_get_icudt_name; + + MONO.mono_wasm_set_runtime_options = mono_wasm_set_runtime_options; + MONO.mono_wasm_add_dbg_command_received = mono_wasm_add_dbg_command_received; + MONO.mono_wasm_send_dbg_command_with_parms = mono_wasm_send_dbg_command_with_parms; + MONO.mono_wasm_send_dbg_command = mono_wasm_send_dbg_command; + MONO.mono_wasm_get_dbg_command_info = mono_wasm_get_dbg_command_info; + MONO.mono_wasm_get_details = mono_wasm_get_details; + MONO.mono_wasm_release_object = mono_wasm_release_object; + MONO.mono_wasm_call_function_on = mono_wasm_call_function_on; + MONO.mono_wasm_debugger_resume = mono_wasm_debugger_resume; + MONO.mono_wasm_detach_debugger = mono_wasm_detach_debugger; + MONO.mono_wasm_runtime_ready = mono_wasm_runtime_ready; + MONO.mono_wasm_raise_debug_event = mono_wasm_raise_debug_event; + MONO.mono_load_runtime_and_bcl_args = mono_load_runtime_and_bcl_args; + MONO.prevent_timer_throttling = prevent_timer_throttling; + MONO.mono_wasm_load_config = mono_wasm_load_config; + MONO.mono_wasm_load_data_archive = mono_wasm_load_data_archive; +} diff --git a/src/mono/wasm/runtime/src/types/emscripten.d.ts b/src/mono/wasm/runtime/src/types/emscripten.d.ts new file mode 100644 index 0000000000000..0c30ec740af01 --- /dev/null +++ b/src/mono/wasm/runtime/src/types/emscripten.d.ts @@ -0,0 +1,56 @@ +// 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 { } +declare interface NativePointer { } + +declare interface MonoString extends ManagedPointer { } +declare interface VoidPtr extends NativePointer { } +declare interface CharPtr extends NativePointer { } +declare interface Int32Ptr extends NativePointer { } +declare interface CharPtrPtr extends NativePointer { } + +declare var ENVIRONMENT_IS_WEB: boolean; +declare var ENVIRONMENT_IS_SHELL: boolean; +declare var ENVIRONMENT_IS_NODE: boolean; +declare var ENVIRONMENT_IS_WORKER: boolean; +declare var LibraryManager: any; + +declare function autoAddDeps(a: object, b: string): void; +declare function mergeInto(a: object, b: object): void; + +// TODO, what's wrong with EXPORTED_RUNTIME_METHODS ? +declare function locateFile(path: string, prefix?: string): string; + +declare var Module: t_Module; + +declare interface t_Module { + HEAP8: Int8Array, + HEAP16: Int16Array; + HEAP32: Int32Array; + HEAPU8: Uint8Array; + HEAPU16: Uint16Array; + HEAPU32: Uint32Array; + HEAPF32: Float32Array; + HEAPF64: Float64Array; + + // this should match emcc -s EXPORTED_FUNCTIONS + _malloc(amnt: number): number; + _free(amn: number): void; + + // this should match emcc -s EXPORTED_RUNTIME_METHODS + print(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: number, value: number, type: string, noSafe?: number | boolean): void; + getValue(ptr: number, type: string, noSafe?: number | boolean): number; + UTF8ToString(arg: CharPtr): string; + UTF8ArrayToString(str: TypedArray, heap: number[] | number, outIdx: number, maxBytesToWrite?: 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; +} + +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/src/types/v8.d.ts b/src/mono/wasm/runtime/src/types/v8.d.ts new file mode 100644 index 0000000000000..9a59e88c408a7 --- /dev/null +++ b/src/mono/wasm/runtime/src/types/v8.d.ts @@ -0,0 +1,5 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +// read is a v8 debugger command +declare function read(name: string): string; \ No newline at end of file diff --git a/src/mono/wasm/runtime/tsconfig.json b/src/mono/wasm/runtime/tsconfig.json new file mode 100644 index 0000000000000..e7242a2ec478e --- /dev/null +++ b/src/mono/wasm/runtime/tsconfig.json @@ -0,0 +1,20 @@ +{ + "compilerOptions": { + "noImplicitAny": true, + "noEmitOnError": true, + "removeComments": false, + "sourceMap": false, + "target": "ES2015", + "moduleResolution": "Node", + "lib": [ + "esnext", + "dom" + ], + "strict": true, + "allowJs": true, + "outDir": "bin", + }, + "exclude": [ + "bin" + ] +} \ No newline at end of file diff --git a/src/mono/wasm/wasm.proj b/src/mono/wasm/wasm.proj index ec769911a2814..5e65625e0c7e9 100644 --- a/src/mono/wasm/wasm.proj +++ b/src/mono/wasm/wasm.proj @@ -66,8 +66,8 @@ <_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=['ccall', 'FS_createPath', 'FS_createDataFile', 'cwrap', 'setValue', 'getValue', 'UTF8ToString', 'UTF8ArrayToString', 'addFunction']"" /> - <_EmccCommonFlags Include="-s "EXPORTED_FUNCTIONS=['_putchar']"" /> + <_EmccCommonFlags Include="-s EXPORTED_RUNTIME_METHODS="['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" /> @@ -164,6 +164,15 @@ DestinationFolder="$(MonoObjDir)" SkipUnchangedFiles="true" /> + + + + + + + + +