diff --git a/eng/liveBuilds.targets b/eng/liveBuilds.targets index 920392bae3260..6462ef23da441 100644 --- a/eng/liveBuilds.targets +++ b/eng/liveBuilds.targets @@ -137,6 +137,8 @@ Include="$(MonoArtifactsPath)\cross\*.*" /> + diff --git a/src/installer/pkg/projects/netcoreapp/src/netcoreapp.depproj b/src/installer/pkg/projects/netcoreapp/src/netcoreapp.depproj index 29f0b67ea8277..7f97f90c9b459 100644 --- a/src/installer/pkg/projects/netcoreapp/src/netcoreapp.depproj +++ b/src/installer/pkg/projects/netcoreapp/src/netcoreapp.depproj @@ -71,11 +71,16 @@ Include="@(MonoCrossFiles)"> runtimes/$(PackageRID)/native/cross - runtimes/$(PackageRID)/native/include/%(RecursiveDir) + + runtimes/$(PackageRID)/native/wasm/runtimes/%(RecursiveDir) + + runtimes/$(CoreCLRCrossTargetComponentDirName)_$(TargetArchitecture)/native diff --git a/src/libraries/pretest.proj b/src/libraries/pretest.proj index d073141959ef5..08b8f5ae1caed 100644 --- a/src/libraries/pretest.proj +++ b/src/libraries/pretest.proj @@ -72,6 +72,14 @@ TargetRuntimeIdentifier="$(PackageRID)" /> + + + + + include/%(RecursiveDir) + + wasm/%(RecursiveDir) + diff --git a/src/mono/mono.proj b/src/mono/mono.proj index c6c35c2adcb22..0ac63489b21ed 100644 --- a/src/mono/mono.proj +++ b/src/mono/mono.proj @@ -944,9 +944,16 @@ Targets="Restore;Build" /> + + + + + - + <_MonoRuntimeFilePath Condition="'$(TargetsWindows)' == 'true' and '$(Platform)' == 'x64'">$(MonoObjDir)x64\Bin\$(Configuration)\mono-2.0-sgen.dll <_MonoRuntimeFilePath Condition="'$(TargetsWindows)' == 'true' and '$(Platform)' == 'x86'">$(MonoObjDir)Win32\Bin\$(Configuration)\mono-2.0-sgen.dll @@ -981,6 +988,18 @@ $(BinDir)cross\opt <_MonoIncludeArtifacts Include="$(MonoObjDir)out\include\**" /> + <_MonoRuntimeArtifacts Condition="'$(TargetsBrowser)' == 'true'" Include="$(MonoObjDir)out\lib\libmono-ee-interp.a"> + $(BinDir)libmono-ee-interp.a + + <_MonoRuntimeArtifacts Condition="'$(TargetsBrowser)' == 'true'" Include="$(MonoObjDir)out\lib\libmono-icall-table.a"> + $(BinDir)libmono-icall-table.a + + <_MonoRuntimeArtifacts Condition="'$(TargetsBrowser)' == 'true'" Include="$(MonoObjDir)out\lib\libmono-ilgen.a"> + $(BinDir)libmono-ilgen.a + + <_MonoRuntimeArtifacts Condition="'$(TargetsBrowser)' == 'true'" Include="$(MonoObjDir)out\lib\libmono-profiler-aot.a"> + $(BinDir)libmono-profiler-aot.a + item.ItemSpec).ToArray (), Assemblies!.Select (item => item.ItemSpec).ToArray ()); + return true; + } + + void GenPInvokeTable (string[] pinvokeModules, string[] assemblies) { + var modules = new Dictionary (); + foreach (var module in pinvokeModules) + modules [module] = module; + + var pinvokes = new List (); + + var resolver = new PathAssemblyResolver (assemblies); + var mlc = new MetadataLoadContext (resolver, "System.Private.CoreLib"); + foreach (var aname in assemblies) { + var a = mlc.LoadFromAssemblyPath (aname); + foreach (var type in a.GetTypes ()) + CollectPInvokes (pinvokes, type); + } + + Log.LogMessage (MessageImportance.Normal, $"Generating pinvoke table to '{OutputPath}'."); + + using (var w = File.CreateText (OutputPath!)) { + EmitPInvokeTable (w, modules, pinvokes); + } + } + + void CollectPInvokes (List pinvokes, Type type) { + foreach (var method in type.GetMethods (BindingFlags.DeclaredOnly|BindingFlags.Public|BindingFlags.NonPublic|BindingFlags.Static|BindingFlags.Instance)) { + if ((method.Attributes & MethodAttributes.PinvokeImpl) == 0) + continue; + var dllimport = method.CustomAttributes.First (attr => attr.AttributeType.Name == "DllImportAttribute"); + var module = (string)dllimport.ConstructorArguments [0].Value!; + var entrypoint = (string)dllimport.NamedArguments.First (arg => arg.MemberName == "EntryPoint").TypedValue.Value!; + pinvokes.Add (new PInvoke (entrypoint, module, method)); + } + } + + void EmitPInvokeTable (StreamWriter w, Dictionary modules, List pinvokes) { + w.WriteLine ("// GENERATED FILE, DO NOT MODIFY"); + w.WriteLine ("typedef struct {"); + w.WriteLine ("const char *name;"); + w.WriteLine ("void *func;"); + w.WriteLine ("} PinvokeImport;"); + w.WriteLine (); + + foreach (var pinvoke in pinvokes) { + if (modules.ContainsKey (pinvoke.Module)) + w.WriteLine (GenPInvokeDecl (pinvoke)); + } + + foreach (var module in modules.Keys) { + string symbol = module.Replace (".", "_") + "_imports"; + w.WriteLine ("static PinvokeImport " + symbol + " [] = {"); + foreach (var pinvoke in pinvokes) { + if (pinvoke.Module == module) + w.WriteLine ("{\"" + pinvoke.EntryPoint + "\", " + pinvoke.EntryPoint + "},"); + } + w.WriteLine ("{NULL, NULL}"); + w.WriteLine ("};"); + } + w.Write ("static void *pinvoke_tables[] = { "); + foreach (var module in modules.Keys) { + string symbol = module.Replace (".", "_") + "_imports"; + w.Write (symbol + ","); + } + w.WriteLine ("};"); + w.Write ("static char *pinvoke_names[] = { "); + foreach (var module in modules.Keys) { + w.Write ("\"" + module + "\"" + ","); + } + w.WriteLine ("};"); + } + + string MapType (Type t) { + string name = t.Name; + if (name == "Void") + return "void"; + else if (name == "Double") + return "double"; + else if (name == "Single") + return "float"; + else if (name == "Int64") + return "int64_t"; + else if (name == "UInt64") + return "uint64_t"; + else + return "int"; + } + + string GenPInvokeDecl (PInvoke pinvoke) { + var sb = new StringBuilder (); + var method = pinvoke.Method; + sb.Append (MapType (method.ReturnType)); + sb.Append ($" {pinvoke.EntryPoint} ("); + int pindex = 0; + var pars = method.GetParameters (); + foreach (var p in pars) { + if (pindex > 0) + sb.Append (","); + sb.Append (MapType (pars [pindex].ParameterType)); + pindex ++; + } + sb.Append (");"); + return sb.ToString (); + } +} + +class PInvoke +{ + public PInvoke (string entry_point, string module, MethodInfo method) { + EntryPoint = entry_point; + Module = module; + Method = method; + } + + public string EntryPoint; + public string Module; + public MethodInfo Method; +} diff --git a/src/mono/msbuild/WasmAppBuilder/WasmAppBuilder.csproj b/src/mono/msbuild/WasmAppBuilder/WasmAppBuilder.csproj new file mode 100644 index 0000000000000..271afb933b491 --- /dev/null +++ b/src/mono/msbuild/WasmAppBuilder/WasmAppBuilder.csproj @@ -0,0 +1,16 @@ + + + Library + false + + + + + + + + + + + + diff --git a/src/mono/wasm/Makefile b/src/mono/wasm/Makefile new file mode 100644 index 0000000000000..78e6a049d843e --- /dev/null +++ b/src/mono/wasm/Makefile @@ -0,0 +1,100 @@ +TOP=$(realpath $(CURDIR)/../../..) +-include Make.config + +# +# These variables are set by wasm.targets +# +EMSDK_PATH?=emsdk +CONFIG?=Release +BINDIR?=$(TOP)/artifacts/bin +OBJDIR?=$(TOP)/artifacts/obj +PINVOKE_TABLE?=$(TOP)/artifacts/obj/mono/Browser.wasm.$(CONFIG)/wasm/pinvoke-table.h +MONO_BIN_DIR?=$(BINDIR)/mono/Browser.wasm.$(CONFIG) +SYS_NATIVE_DIR?=$(BINDIR)/native/net5.0-Browser-$(CONFIG)-wasm/ + +all: build-native + +# +# EMSCRIPTEN SETUP +# +# If EMSDK_PATH is not set by the caller, download and setup a local emsdk install. +# + +EMSCRIPTEN_VERSION=1.39.11 +EMSDK_LOCAL_PATH=emsdk +EMCC=source $(CURDIR)/emsdk_env.sh && emcc + +.stamp-wasm-install-and-select-$(EMSCRIPTEN_VERSION): + rm -rf $(EMSDK_LOCAL_PATH) + git clone https://github.com/emscripten-core/emsdk.git $(EMSDK_LOCAL_PATH) + cd $(EMSDK_LOCAL_PATH) && git checkout $(EMSCRIPTEN_VERSION) + cd $(EMSDK_LOCAL_PATH) && ./emsdk install $(EMSCRIPTEN_VERSION) + cd $(EMSDK_LOCAL_PATH) && ./emsdk activate --embedded $(EMSCRIPTEN_VERSION) + touch $@ + +ifeq ($(EMSDK_PATH),emsdk) +provision-wasm: .stamp-wasm-install-and-select-$(EMSCRIPTEN_VERSION) +else +provision-wasm: +endif + +# emsdk_env.sh calls emsdk construct_env which is a bit slow so make a copy +emsdk_env.sh: | provision-wasm + cd $(EMSDK_PATH) && ./emsdk construct_env $(CURDIR)/emsdk_env.sh + +MONO_OBJ_DIR=$(OBJDIR)/mono/Browser.wasm.$(CONFIG) +MONO_LIBS = $(MONO_BIN_DIR)/{libmono-ee-interp.a,libmono.a,libmono-ilgen.a,libmono-icall-table.a} ${SYS_NATIVE_DIR}/libSystem.Native.a +MONO_INCLUDE_DIR=$(MONO_BIN_DIR)/include/mono-2.0 +BUILDS_BIN_DIR=$(MONO_BIN_DIR)/wasm/runtimes +BUILDS_OBJ_DIR=$(MONO_OBJ_DIR)/wasm/runtimes + +EMCC_FLAGS=--profiling-funcs -s WASM=1 -s ALLOW_MEMORY_GROWTH=1 -s BINARYEN=1 -s ALIASING_FUNCTION_POINTERS=0 -s NO_EXIT_RUNTIME=1 -s "EXTRA_EXPORTED_RUNTIME_METHODS=['ccall', 'FS_createPath', 'FS_createDataFile', 'cwrap', 'setValue', 'getValue', 'UTF8ToString', 'addFunction']" -s "EXPORTED_FUNCTIONS=['_putchar']" --source-map-base http://example.com -s WASM_OBJECT_FILES=0 -s FORCE_FILESYSTEM=1 -s USE_ZLIB=1 +EMCC_DEBUG_FLAGS =-g -Os -s ASSERTIONS=1 -DENABLE_NETCORE=1 +EMCC_RELEASE_FLAGS=-Oz --llvm-opts 2 --llvm-lto 1 -DENABLE_NETCORE=1 + +# +# Interpreter builds +# + +# $(1) - name +# $(2) - runtime dir +# $(3) - EMCC_FLAGS +# $(4) - libs +define InterpBuildTemplate + +$(BUILDS_BIN_DIR)/$(1)/: + mkdir -p $$@ + +$(BUILDS_OBJ_DIR)/$(1)/: + mkdir -p $$@ + +$(BUILDS_BIN_DIR)/$(1)/dotnet.js: $(BUILDS_OBJ_DIR)/$(1)/driver.o $(BUILDS_OBJ_DIR)/$(1)/corebindings.o runtime/library_mono.js runtime/binding_support.js runtime/dotnet_support.js $(5) | $(BUILDS_BIN_DIR)/$(1)/ emsdk_env.sh + $(EMCC) $(EMCC_FLAGS) $(3) --js-library runtime/library_mono.js --js-library runtime/binding_support.js --js-library runtime/dotnet_support.js $(BUILDS_OBJ_DIR)/$(1)/driver.o $(BUILDS_OBJ_DIR)/$(1)/corebindings.o $(4) -o $(BUILDS_BIN_DIR)/$(1)/dotnet.js + +$(BUILDS_OBJ_DIR)/$(1)/pinvoke-table.h: $(PINVOKE_TABLE) | $(BUILDS_OBJ_DIR)/$(1)/ + if cmp -s $(PINVOKE_TABLE) $$@ ; then : ; else cp $(PINVOKE_TABLE) $$@ ; fi + +$(BUILDS_OBJ_DIR)/$(1)/driver.o: runtime/driver.c runtime/corebindings.c $(BUILDS_OBJ_DIR)/$(1)/pinvoke-table.h $(5) | $(BUILDS_OBJ_DIR)/$(1)/ emsdk_env.sh + $(EMCC) $(EMCC_FLAGS) $(3) -Oz -DCORE_BINDINGS -I$(BUILDS_OBJ_DIR)/$(1) -I$(MONO_INCLUDE_DIR) runtime/driver.c -c -o $$@ + +$(BUILDS_OBJ_DIR)/$(1)/corebindings.o: runtime/corebindings.c | $(BUILDS_OBJ_DIR)/$(1)/ emsdk_env.sh + $(EMCC) $(3) -Oz -I$(MONO_INCLUDE_DIR) runtime/corebindings.c -c -o $$@ + +build-native: $(BUILDS_BIN_DIR)/$(1)/dotnet.js + +build-interp-$(1): $(BUILDS_BIN_DIR)/$(1)/dotnet.js + +endef + +$(eval $(call InterpBuildTemplate,debug,,$(EMCC_DEBUG_FLAGS),$(MONO_LIBS))) +$(eval $(call InterpBuildTemplate,release,,$(EMCC_RELEASE_FLAGS),$(MONO_LIBS))) + +build: + EMSDK_PATH=$(PWD)/wasm/emsdk ../../../.dotnet/dotnet build /p:TargetArchitecture=wasm /p:TargetOS=Browser /p:Configuration=Release + +clean-emsdk: + $(RM) -rf $(EMSDK_LOCAL_PATH) + +clean: + $(RM) -rf $(BUILDS_BIN_DIR) $(BUILDS_OBJ_DIR) + $(RM) emsdk_env.sh diff --git a/src/mono/wasm/runtime/binding_support.js b/src/mono/wasm/runtime/binding_support.js new file mode 100644 index 0000000000000..46b9383373e83 --- /dev/null +++ b/src/mono/wasm/runtime/binding_support.js @@ -0,0 +1,1277 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +var BindingSupportLib = { + $BINDING__postset: 'BINDING.export_functions (Module);', + $BINDING: { + BINDING_ASM: "[WebAssembly.Bindings]WebAssembly.Runtime", + mono_wasm_object_registry: [], + mono_wasm_ref_counter: 0, + mono_wasm_free_list: [], + mono_wasm_marshal_enum_as_int: false, + mono_bindings_init: function (binding_asm) { + this.BINDING_ASM = binding_asm; + }, + + export_functions: function (module) { + module ["mono_bindings_init"] = BINDING.mono_bindings_init.bind(BINDING); + module ["mono_method_invoke"] = BINDING.call_method.bind(BINDING); + module ["mono_method_get_call_signature"] = BINDING.mono_method_get_call_signature.bind(BINDING); + module ["mono_method_resolve"] = BINDING.resolve_method_fqn.bind(BINDING); + module ["mono_bind_static_method"] = BINDING.bind_static_method.bind(BINDING); + module ["mono_call_static_method"] = BINDING.call_static_method.bind(BINDING); + module ["mono_bind_assembly_entry_point"] = BINDING.bind_assembly_entry_point.bind(BINDING); + module ["mono_call_assembly_entry_point"] = BINDING.call_assembly_entry_point.bind(BINDING); + }, + + bindings_lazy_init: function () { + if (this.init) + return; + + this.assembly_load = Module.cwrap ('mono_wasm_assembly_load', 'number', ['string']); + this.find_class = Module.cwrap ('mono_wasm_assembly_find_class', 'number', ['number', 'string', 'string']); + this.find_method = Module.cwrap ('mono_wasm_assembly_find_method', 'number', ['number', 'string', 'number']); + this.invoke_method = Module.cwrap ('mono_wasm_invoke_method', 'number', ['number', 'number', 'number', 'number']); + this.mono_string_get_utf8 = Module.cwrap ('mono_wasm_string_get_utf8', 'number', ['number']); + this.js_string_to_mono_string = Module.cwrap ('mono_wasm_string_from_js', 'number', ['string']); + this.mono_get_obj_type = Module.cwrap ('mono_wasm_get_obj_type', 'number', ['number']); + this.mono_unbox_int = Module.cwrap ('mono_unbox_int', 'number', ['number']); + this.mono_unbox_float = Module.cwrap ('mono_wasm_unbox_float', 'number', ['number']); + this.mono_array_length = Module.cwrap ('mono_wasm_array_length', 'number', ['number']); + this.mono_array_get = Module.cwrap ('mono_wasm_array_get', 'number', ['number', 'number']); + this.mono_obj_array_new = Module.cwrap ('mono_wasm_obj_array_new', 'number', ['number']); + this.mono_obj_array_set = Module.cwrap ('mono_wasm_obj_array_set', 'void', ['number', 'number', 'number']); + this.mono_unbox_enum = Module.cwrap ('mono_wasm_unbox_enum', 'number', ['number']); + this.assembly_get_entry_point = Module.cwrap ('mono_wasm_assembly_get_entry_point', 'number', ['number']); + + // receives a byteoffset into allocated Heap with a size. + this.mono_typed_array_new = Module.cwrap ('mono_wasm_typed_array_new', 'number', ['number','number','number','number']); + + var binding_fqn_asm = this.BINDING_ASM.substring(this.BINDING_ASM.indexOf ("[") + 1, this.BINDING_ASM.indexOf ("]")).trim(); + var binding_fqn_class = this.BINDING_ASM.substring (this.BINDING_ASM.indexOf ("]") + 1).trim(); + + this.binding_module = this.assembly_load (binding_fqn_asm); + if (!this.binding_module) + throw "Can't find bindings module assembly: " + binding_fqn_asm; + + if (binding_fqn_class !== null && typeof binding_fqn_class !== "undefined") + { + var namespace = "WebAssembly"; + var classname = binding_fqn_class.length > 0 ? binding_fqn_class : "Runtime"; + if (binding_fqn_class.indexOf(".") != -1) { + var idx = binding_fqn_class.lastIndexOf("."); + namespace = binding_fqn_class.substring (0, idx); + classname = binding_fqn_class.substring (idx + 1); + } + } + + var wasm_runtime_class = this.find_class (this.binding_module, namespace, classname) + if (!wasm_runtime_class) + throw "Can't find " + binding_fqn_class + " class"; + + var get_method = function(method_name) { + var res = BINDING.find_method (wasm_runtime_class, method_name, -1) + if (!res) + throw "Can't find method " + namespace + "." + classname + ":" + method_name; + return res; + } + this.bind_js_obj = get_method ("BindJSObject"); + this.bind_core_clr_obj = get_method ("BindCoreCLRObject"); + this.bind_existing_obj = get_method ("BindExistingObject"); + this.unbind_js_obj = get_method ("UnBindJSObject"); + this.unbind_js_obj_and_free = get_method ("UnBindJSObjectAndFree"); + this.unbind_raw_obj_and_free = get_method ("UnBindRawJSObjectAndFree"); + this.get_js_id = get_method ("GetJSObjectId"); + this.get_raw_mono_obj = get_method ("GetMonoObject"); + + this.box_js_int = get_method ("BoxInt"); + this.box_js_double = get_method ("BoxDouble"); + this.box_js_bool = get_method ("BoxBool"); + this.is_simple_array = get_method ("IsSimpleArray"); + this.get_core_type = get_method ("GetCoreType"); + this.setup_js_cont = get_method ("SetupJSContinuation"); + + this.create_tcs = get_method ("CreateTaskSource"); + this.set_tcs_result = get_method ("SetTaskSourceResult"); + this.set_tcs_failure = get_method ("SetTaskSourceFailure"); + this.tcs_get_task_and_bind = get_method ("GetTaskAndBind"); + this.get_call_sig = get_method ("GetCallSignature"); + + this.object_to_string = get_method ("ObjectToString"); + this.get_date_value = get_method ("GetDateValue"); + this.create_date_time = get_method ("CreateDateTime"); + this.create_uri = get_method ("CreateUri"); + + this.object_to_enum = get_method ("ObjectToEnum"); + this.init = true; + }, + + get_js_obj: function (js_handle) { + if (js_handle > 0) + return this.mono_wasm_require_handle(js_handle); + return null; + }, + + conv_string: function (mono_obj) { + return MONO.string_decoder.copy (mono_obj); + }, + + is_nested_array: function (ele) { + return this.call_method (this.is_simple_array, null, "mi", [ ele ]); + }, + + mono_array_to_js_array: function (mono_array) { + if (mono_array == 0) + return null; + + var res = []; + var len = this.mono_array_length (mono_array); + for (var i = 0; i < len; ++i) + { + var ele = this.mono_array_get (mono_array, i); + if (this.is_nested_array(ele)) + res.push(this.mono_array_to_js_array(ele)); + else + res.push (this.unbox_mono_obj (ele)); + } + + return res; + }, + + js_array_to_mono_array: function (js_array) { + var mono_array = this.mono_obj_array_new (js_array.length); + for (var i = 0; i < js_array.length; ++i) { + this.mono_obj_array_set (mono_array, i, this.js_to_mono_obj (js_array [i])); + } + return mono_array; + }, + + unbox_mono_obj: function (mono_obj) { + if (mono_obj == 0) + return undefined; + var type = this.mono_get_obj_type (mono_obj); + //See MARSHAL_TYPE_ defines in driver.c + switch (type) { + case 1: // int + return this.mono_unbox_int (mono_obj); + case 2: // float + return this.mono_unbox_float (mono_obj); + case 3: //string + return this.conv_string (mono_obj); + case 4: //vts + throw new Error ("no idea on how to unbox value types"); + case 5: { // delegate + var obj = this.extract_js_obj (mono_obj); + return function () { + return BINDING.invoke_delegate (obj, arguments); + }; + } + case 6: {// Task + + if (typeof Promise === "undefined" || typeof Promise.resolve === "undefined") + throw new Error ("Promises are not supported thus C# Tasks can not work in this context."); + + var obj = this.extract_js_obj (mono_obj); + var cont_obj = null; + var promise = new Promise (function (resolve, reject) { + cont_obj = { + resolve: resolve, + reject: reject + }; + }); + + this.call_method (this.setup_js_cont, null, "mo", [ mono_obj, cont_obj ]); + obj.__mono_js_cont__ = cont_obj.__mono_gchandle__; + cont_obj.__mono_js_task__ = obj.__mono_gchandle__; + return promise; + } + + case 7: // ref type + return this.extract_js_obj (mono_obj); + + case 8: // bool + return this.mono_unbox_int (mono_obj) != 0; + + case 9: // enum + + if(this.mono_wasm_marshal_enum_as_int) + { + return this.mono_unbox_enum (mono_obj); + } + else + { + enumValue = this.call_method(this.object_to_string, null, "m", [ mono_obj ]); + } + + return enumValue; + + + case 11: + case 12: + case 13: + case 14: + case 15: + case 16: + case 17: + case 18: + { + throw new Error ("Marshalling of primitive arrays are not supported. Use the corresponding TypedArray instead."); + } + case 20: // clr .NET DateTime + var dateValue = this.call_method(this.get_date_value, null, "md", [ mono_obj ]); + return new Date(dateValue); + case 21: // clr .NET DateTimeOffset + var dateoffsetValue = this.call_method(this.object_to_string, null, "m", [ mono_obj ]); + return dateoffsetValue; + case 22: // clr .NET Uri + var uriValue = this.call_method(this.object_to_string, null, "m", [ mono_obj ]); + return uriValue; + default: + throw new Error ("no idea on how to unbox object kind " + type); + } + }, + + create_task_completion_source: function () { + return this.call_method (this.create_tcs, null, "i", [ -1 ]); + }, + + set_task_result: function (tcs, result) { + tcs.is_mono_tcs_result_set = true; + this.call_method (this.set_tcs_result, null, "oo", [ tcs, result ]); + if (tcs.is_mono_tcs_task_bound) + this.free_task_completion_source(tcs); + }, + + set_task_failure: function (tcs, reason) { + tcs.is_mono_tcs_result_set = true; + this.call_method (this.set_tcs_failure, null, "os", [ tcs, reason.toString () ]); + if (tcs.is_mono_tcs_task_bound) + this.free_task_completion_source(tcs); + }, + + // https://github.com/Planeshifter/emscripten-examples/blob/master/01_PassingArrays/sum_post.js + js_typedarray_to_heap: function(typedArray){ + var numBytes = typedArray.length * typedArray.BYTES_PER_ELEMENT; + var ptr = Module._malloc(numBytes); + var heapBytes = new Uint8Array(Module.HEAPU8.buffer, ptr, numBytes); + heapBytes.set(new Uint8Array(typedArray.buffer, typedArray.byteOffset, numBytes)); + return heapBytes; + }, + js_to_mono_obj: function (js_obj) { + this.bindings_lazy_init (); + + // determines if the javascript object is a Promise or Promise like which can happen + // when using an external Promise library. The javascript object should be marshalled + // as managed Task objects. + // + // Example is when Bluebird is included in a web page using a script tag, it overwrites the + // global Promise object by default with its own version of Promise. + function isThenable() { + // When using an external Promise library the Promise.resolve may not be sufficient + // to identify the object as a Promise. + return Promise.resolve(js_obj) === js_obj || + ((typeof js_obj === "object" || typeof js_obj === "function") && typeof js_obj.then === "function") + } + + switch (true) { + case js_obj === null: + case typeof js_obj === "undefined": + return 0; + case typeof js_obj === "number": + if (parseInt(js_obj) == js_obj) + return this.call_method (this.box_js_int, null, "im", [ js_obj ]); + return this.call_method (this.box_js_double, null, "dm", [ js_obj ]); + case typeof js_obj === "string": + return this.js_string_to_mono_string (js_obj); + case typeof js_obj === "boolean": + return this.call_method (this.box_js_bool, null, "im", [ js_obj ]); + case isThenable() === true: + var the_task = this.try_extract_mono_obj (js_obj); + if (the_task) + return the_task; + var tcs = this.create_task_completion_source (); + js_obj.then (function (result) { + BINDING.set_task_result (tcs, result); + }, function (reason) { + BINDING.set_task_failure (tcs, reason); + }) + return this.get_task_and_bind (tcs, js_obj); + case js_obj.constructor.name === "Date": + // We may need to take into account the TimeZone Offset + return this.call_method(this.create_date_time, null, "dm", [ js_obj.getTime() ]); + default: + return this.extract_mono_obj (js_obj); + } + }, + js_to_mono_uri: function (js_obj) { + this.bindings_lazy_init (); + + switch (true) { + case js_obj === null: + case typeof js_obj === "undefined": + return 0; + case typeof js_obj === "string": + return this.call_method(this.create_uri, null, "sm", [ js_obj ]) + default: + return this.extract_mono_obj (js_obj); + } + }, + js_typed_array_to_array : function (js_obj) { + + // JavaScript typed arrays are array-like objects and provide a mechanism for accessing + // raw binary data. (...) To achieve maximum flexibility and efficiency, JavaScript typed arrays + // split the implementation into buffers and views. A buffer (implemented by the ArrayBuffer object) + // is an object representing a chunk of data; it has no format to speak of, and offers no + // mechanism for accessing its contents. In order to access the memory contained in a buffer, + // you need to use a view. A view provides a context — that is, a data type, starting offset, + // and number of elements — that turns the data into an actual typed array. + // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Typed_arrays + if (!!(js_obj.buffer instanceof ArrayBuffer && js_obj.BYTES_PER_ELEMENT)) + { + var arrayType = 0; + if (js_obj instanceof Int8Array) + arrayType = 11; + if (js_obj instanceof Uint8Array) + arrayType = 12; + if (js_obj instanceof Uint8ClampedArray) + arrayType = 12; + if (js_obj instanceof Int16Array) + arrayType = 13; + if (js_obj instanceof Uint16Array) + arrayType = 14; + if (js_obj instanceof Int32Array) + arrayType = 15; + if (js_obj instanceof Uint32Array) + arrayType = 16; + if (js_obj instanceof Float32Array) + arrayType = 17; + if (js_obj instanceof Float64Array) + arrayType = 18; + + var heapBytes = this.js_typedarray_to_heap(js_obj); + var bufferArray = this.mono_typed_array_new(heapBytes.byteOffset, js_obj.length, js_obj.BYTES_PER_ELEMENT, arrayType); + Module._free(heapBytes.byteOffset); + return bufferArray; + } + else { + throw new Error("Object '" + js_obj + "' is not a typed array"); + } + + + }, + // Copy the existing typed array to the heap pointed to by the pinned array address + // typed array memory -> copy to heap -> address of managed pinned array + typedarray_copy_to : function (typed_array, pinned_array, begin, end, bytes_per_element) { + + // JavaScript typed arrays are array-like objects and provide a mechanism for accessing + // raw binary data. (...) To achieve maximum flexibility and efficiency, JavaScript typed arrays + // split the implementation into buffers and views. A buffer (implemented by the ArrayBuffer object) + // is an object representing a chunk of data; it has no format to speak of, and offers no + // mechanism for accessing its contents. In order to access the memory contained in a buffer, + // you need to use a view. A view provides a context — that is, a data type, starting offset, + // and number of elements — that turns the data into an actual typed array. + // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Typed_arrays + if (!!(typed_array.buffer instanceof ArrayBuffer && typed_array.BYTES_PER_ELEMENT)) + { + // Some sanity checks of what is being asked of us + // lets play it safe and throw an error here instead of assuming to much. + // Better safe than sorry later + if (bytes_per_element !== typed_array.BYTES_PER_ELEMENT) + throw new Error("Inconsistent element sizes: TypedArray.BYTES_PER_ELEMENT '" + typed_array.BYTES_PER_ELEMENT + "' sizeof managed element: '" + bytes_per_element + "'"); + + // how much space we have to work with + var num_of_bytes = (end - begin) * bytes_per_element; + // how much typed buffer space are we talking about + var view_bytes = typed_array.length * typed_array.BYTES_PER_ELEMENT; + // only use what is needed. + if (num_of_bytes > view_bytes) + num_of_bytes = view_bytes; + + // offset index into the view + var offset = begin * bytes_per_element; + + // Create a view over the heap pointed to by the pinned array address + var heapBytes = new Uint8Array(Module.HEAPU8.buffer, pinned_array + offset, num_of_bytes); + // Copy the bytes of the typed array to the heap. + heapBytes.set(new Uint8Array(typed_array.buffer, typed_array.byteOffset, num_of_bytes)); + + return num_of_bytes; + } + else { + throw new Error("Object '" + typed_array + "' is not a typed array"); + } + + }, + // Copy the pinned array address from pinned_array allocated on the heap to the typed array. + // adress of managed pinned array -> copy from heap -> typed array memory + typedarray_copy_from : function (typed_array, pinned_array, begin, end, bytes_per_element) { + + // JavaScript typed arrays are array-like objects and provide a mechanism for accessing + // raw binary data. (...) To achieve maximum flexibility and efficiency, JavaScript typed arrays + // split the implementation into buffers and views. A buffer (implemented by the ArrayBuffer object) + // is an object representing a chunk of data; it has no format to speak of, and offers no + // mechanism for accessing its contents. In order to access the memory contained in a buffer, + // you need to use a view. A view provides a context — that is, a data type, starting offset, + // and number of elements — that turns the data into an actual typed array. + // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Typed_arrays + if (!!(typed_array.buffer instanceof ArrayBuffer && typed_array.BYTES_PER_ELEMENT)) + { + // Some sanity checks of what is being asked of us + // lets play it safe and throw an error here instead of assuming to much. + // Better safe than sorry later + if (bytes_per_element !== typed_array.BYTES_PER_ELEMENT) + throw new Error("Inconsistent element sizes: TypedArray.BYTES_PER_ELEMENT '" + typed_array.BYTES_PER_ELEMENT + "' sizeof managed element: '" + bytes_per_element + "'"); + + // how much space we have to work with + var num_of_bytes = (end - begin) * bytes_per_element; + // how much typed buffer space are we talking about + var view_bytes = typed_array.length * typed_array.BYTES_PER_ELEMENT; + // only use what is needed. + if (num_of_bytes > view_bytes) + num_of_bytes = view_bytes; + + // Create a new view for mapping + var typedarrayBytes = new Uint8Array(typed_array.buffer, 0, num_of_bytes); + // offset index into the view + var offset = begin * bytes_per_element; + // Set view bytes to value from HEAPU8 + typedarrayBytes.set(Module.HEAPU8.subarray(pinned_array + offset, pinned_array + offset + num_of_bytes)); + return num_of_bytes; + } + else { + throw new Error("Object '" + typed_array + "' is not a typed array"); + } + + }, + // Creates a new typed array from pinned array address from pinned_array allocated on the heap to the typed array. + // adress of managed pinned array -> copy from heap -> typed array memory + typed_array_from : function (pinned_array, begin, end, bytes_per_element, type) { + + // typed array + var newTypedArray = 0; + + switch (type) + { + case 5: + newTypedArray = new Int8Array(end - begin); + break; + case 6: + newTypedArray = new Uint8Array(end - begin); + break; + case 7: + newTypedArray = new Int16Array(end - begin); + break; + case 8: + newTypedArray = new Uint16Array(end - begin); + break; + case 9: + newTypedArray = new Int32Array(end - begin); + break; + case 10: + newTypedArray = new Uint32Array(end - begin); + break; + case 13: + newTypedArray = new Float32Array(end - begin); + break; + case 14: + newTypedArray = new Float64Array(end - begin); + break; + case 15: // This is a special case because the typed array is also byte[] + newTypedArray = new Uint8ClampedArray(end - begin); + break; + } + + this.typedarray_copy_from(newTypedArray, pinned_array, begin, end, bytes_per_element); + return newTypedArray; + }, + js_to_mono_enum: function (method, parmIdx, js_obj) { + this.bindings_lazy_init (); + + if (js_obj === null || typeof js_obj === "undefined") + return 0; + + var monoObj = this.js_to_mono_obj(js_obj); + // Check enum contract + var monoEnum = this.call_method(this.object_to_enum, null, "iimm", [ method, parmIdx, monoObj ]) + // return the unboxed enum value. + return this.mono_unbox_enum(monoEnum); + }, + wasm_binding_obj_new: function (js_obj_id, type) + { + return this.call_method (this.bind_js_obj, null, "io", [js_obj_id, type]); + }, + wasm_bind_existing: function (mono_obj, js_id) + { + return this.call_method (this.bind_existing_obj, null, "mi", [mono_obj, js_id]); + }, + + wasm_bind_core_clr_obj: function (js_id, gc_handle) + { + return this.call_method (this.bind_core_clr_obj, null, "ii", [js_id, gc_handle]); + }, + + wasm_unbind_js_obj: function (js_obj_id) + { + this.call_method (this.unbind_js_obj, null, "i", [js_obj_id]); + }, + + wasm_unbind_js_obj_and_free: function (js_obj_id) + { + this.call_method (this.unbind_js_obj_and_free, null, "i", [js_obj_id]); + }, + + wasm_get_js_id: function (mono_obj) + { + return this.call_method (this.get_js_id, null, "m", [mono_obj]); + }, + + wasm_get_raw_obj: function (gchandle) + { + return this.call_method (this.get_raw_mono_obj, null, "im", [gchandle]); + }, + + try_extract_mono_obj:function (js_obj) { + if (js_obj === null || typeof js_obj === "undefined" || typeof js_obj.__mono_gchandle__ === "undefined") + return 0; + return this.wasm_get_raw_obj (js_obj.__mono_gchandle__); + }, + + mono_method_get_call_signature: function(method) { + this.bindings_lazy_init (); + + return this.call_method (this.get_call_sig, null, "i", [ method ]); + }, + + get_task_and_bind: function (tcs, js_obj) { + var gc_handle = this.mono_wasm_free_list.length ? this.mono_wasm_free_list.pop() : this.mono_wasm_ref_counter++; + var task_gchandle = this.call_method (this.tcs_get_task_and_bind, null, "oi", [ tcs, gc_handle + 1 ]); + js_obj.__mono_gchandle__ = task_gchandle; + this.mono_wasm_object_registry[gc_handle] = js_obj; + this.free_task_completion_source(tcs); + tcs.is_mono_tcs_task_bound = true; + js_obj.__mono_bound_tcs__ = tcs.__mono_gchandle__; + tcs.__mono_bound_task__ = js_obj.__mono_gchandle__; + return this.wasm_get_raw_obj (js_obj.__mono_gchandle__); + }, + + free_task_completion_source: function (tcs) { + if (tcs.is_mono_tcs_result_set) + { + this.call_method (this.unbind_raw_obj_and_free, null, "ii", [ tcs.__mono_gchandle__ ]); + } + if (tcs.__mono_bound_task__) + { + this.call_method (this.unbind_raw_obj_and_free, null, "ii", [ tcs.__mono_bound_task__ ]); + } + }, + + extract_mono_obj: function (js_obj) { + + if (js_obj === null || typeof js_obj === "undefined") + return 0; + + if (!js_obj.is_mono_bridged_obj) { + var gc_handle = this.mono_wasm_register_obj(js_obj); + return this.wasm_get_raw_obj (gc_handle); + } + + + return this.wasm_get_raw_obj (js_obj.__mono_gchandle__); + }, + + extract_js_obj: function (mono_obj) { + if (mono_obj == 0) + return null; + + var js_id = this.wasm_get_js_id (mono_obj); + if (js_id > 0) + return this.mono_wasm_require_handle(js_id); + + var gcHandle = this.mono_wasm_free_list.length ? this.mono_wasm_free_list.pop() : this.mono_wasm_ref_counter++; + var js_obj = { + __mono_gchandle__: this.wasm_bind_existing(mono_obj, gcHandle + 1), + is_mono_bridged_obj: true + }; + + this.mono_wasm_object_registry[gcHandle] = js_obj; + return js_obj; + }, + + /* + args_marshal is a string with one character per parameter that tells how to marshal it, here are the valid values: + + i: int32 + j: int32 - Enum with underlying type of int32 + l: int64 + k: int64 - Enum with underlying type of int64 + f: float + d: double + s: string + o: js object will be converted to a C# object (this will box numbers/bool/promises) + m: raw mono object. Don't use it unless you know what you're doing + + additionally you can append 'm' to args_marshal beyond `args.length` if you don't want the return value marshaled + */ + call_method: function (method, this_arg, args_marshal, args) { + this.bindings_lazy_init (); + + // Allocate memory for error + var has_args = args !== null && typeof args !== "undefined" && args.length > 0; + var has_args_marshal = args_marshal !== null && typeof args_marshal !== "undefined" && args_marshal.length > 0; + + if (has_args_marshal && (!has_args || args.length > args_marshal.length)) + throw Error("Parameter count mismatch."); + + var args_start = null; + var buffer = null; + var exception_out = null; + + // check if the method signature needs argument mashalling + if (has_args_marshal && has_args) { + var i; + + var converters = this.converters; + if (!converters) { + converters = new Map (); + converters.set ('m', { steps: [{ }], size: 0}); + converters.set ('s', { steps: [{ convert: this.js_string_to_mono_string.bind (this)}], size: 0}); + converters.set ('o', { steps: [{ convert: this.js_to_mono_obj.bind (this)}], size: 0}); + converters.set ('u', { steps: [{ convert: this.js_to_mono_uri.bind (this)}], size: 0}); + converters.set ('k', { steps: [{ convert: this.js_to_mono_enum.bind (this), indirect: 'i64'}], size: 8}); + converters.set ('j', { steps: [{ convert: this.js_to_mono_enum.bind (this), indirect: 'i32'}], size: 8}); + converters.set ('i', { steps: [{ indirect: 'i32'}], size: 8}); + converters.set ('l', { steps: [{ indirect: 'i64'}], size: 8}); + converters.set ('f', { steps: [{ indirect: 'float'}], size: 8}); + converters.set ('d', { steps: [{ indirect: 'double'}], size: 8}); + this.converters = converters; + } + + var converter = converters.get (args_marshal); + if (!converter) { + var steps = []; + var size = 0; + + for (i = 0; i < args_marshal.length; ++i) { + var conv = this.converters.get (args_marshal[i]); + if (!conv) + throw Error ("Unknown parameter type " + type); + + steps.push (conv.steps[0]); + size += conv.size; + } + converter = { steps: steps, size: size }; + converters.set (args_marshal, converter); + } + + // assume at least 8 byte alignment from malloc + buffer = Module._malloc (converter.size + (args.length * 4) + 4); + var indirect_start = buffer; // buffer + buffer % 8 + exception_out = indirect_start + converter.size; + args_start = exception_out + 4; + + var slot = args_start; + var indirect_value = indirect_start; + for (i = 0; i < args.length; ++i) { + var handler = converter.steps[i]; + var obj = handler.convert ? handler.convert (args[i], method, i) : args[i]; + + if (handler.indirect) { + Module.setValue (indirect_value, obj, handler.indirect); + obj = indirect_value; + indirect_value += 8; + } + + Module.setValue (slot, obj, "*"); + slot += 4; + } + } else { + // only marshal the exception + exception_out = buffer = Module._malloc (4); + } + + Module.setValue (exception_out, 0, "*"); + + var res = this.invoke_method (method, this_arg, args_start, exception_out); + var eh_res = Module.getValue (exception_out, "*"); + + Module._free (buffer); + + if (eh_res != 0) { + var msg = this.conv_string (res); + throw new Error (msg); //the convention is that invoke_method ToString () any outgoing exception + } + + if (has_args_marshal && has_args) { + if (args_marshal.length >= args.length && args_marshal [args.length] === "m") + return res; + } + + return this.unbox_mono_obj (res); + }, + + invoke_delegate: function (delegate_obj, js_args) { + this.bindings_lazy_init (); + + if (!this.delegate_dynamic_invoke) { + if (!this.corlib) + this.corlib = this.assembly_load ("mscorlib"); + if (!this.delegate_class) + this.delegate_class = this.find_class (this.corlib, "System", "Delegate"); + if (!this.delegate_class) + { + throw new Error("System.Delegate class can not be resolved."); + } + this.delegate_dynamic_invoke = this.find_method (this.delegate_class, "DynamicInvoke", -1); + } + var mono_args = this.js_array_to_mono_array (js_args); + if (!this.delegate_dynamic_invoke) + throw new Error("System.Delegate.DynamicInvoke method can not be resolved."); + // Note: the single 'm' passed here is causing problems with AOT. Changed to "mo" again. + // This may need more analysis if causes problems again. + return this.call_method (this.delegate_dynamic_invoke, this.extract_mono_obj (delegate_obj), "mo", [ mono_args ]); + }, + + resolve_method_fqn: function (fqn) { + var assembly = fqn.substring(fqn.indexOf ("[") + 1, fqn.indexOf ("]")).trim(); + fqn = fqn.substring (fqn.indexOf ("]") + 1).trim(); + + var methodname = fqn.substring(fqn.indexOf (":") + 1); + fqn = fqn.substring (0, fqn.indexOf (":")).trim (); + + var namespace = ""; + var classname = fqn; + if (fqn.indexOf(".") != -1) { + var idx = fqn.lastIndexOf("."); + namespace = fqn.substring (0, idx); + classname = fqn.substring (idx + 1); + } + + var asm = this.assembly_load (assembly); + if (!asm) + throw new Error ("Could not find assembly: " + assembly); + + var klass = this.find_class(asm, namespace, classname); + if (!klass) + throw new Error ("Could not find class: " + namespace + ":" +classname); + + var method = this.find_method (klass, methodname, -1); + if (!method) + throw new Error ("Could not find method: " + methodname); + return method; + }, + + call_static_method: function (fqn, args, signature) { + this.bindings_lazy_init (); + + var method = this.resolve_method_fqn (fqn); + + if (typeof signature === "undefined") + signature = Module.mono_method_get_call_signature (method); + + return this.call_method (method, null, signature, args); + }, + + bind_static_method: function (fqn, signature) { + this.bindings_lazy_init (); + + var method = this.resolve_method_fqn (fqn); + + if (typeof signature === "undefined") + signature = Module.mono_method_get_call_signature (method); + + return function() { + return BINDING.call_method (method, null, signature, arguments); + }; + }, + bind_assembly_entry_point: function (assembly) { + this.bindings_lazy_init (); + + var asm = this.assembly_load (assembly); + if (!asm) + throw new Error ("Could not find assembly: " + assembly); + + var method = this.assembly_get_entry_point(asm); + if (!method) + throw new Error ("Could not find entry point for assembly: " + assembly); + + if (typeof signature === "undefined") + signature = Module.mono_method_get_call_signature (method); + + return function() { + return BINDING.call_method (method, null, signature, arguments); + }; + }, + call_assembly_entry_point: function (assembly, args, signature) { + this.bindings_lazy_init (); + + var asm = this.assembly_load (assembly); + if (!asm) + throw new Error ("Could not find assembly: " + assembly); + + var method = this.assembly_get_entry_point(asm); + if (!method) + throw new Error ("Could not find entry point for assembly: " + assembly); + + if (typeof signature === "undefined") + signature = Module.mono_method_get_call_signature (method); + + return this.call_method (method, null, signature, args); + }, + wasm_get_core_type: function (obj) + { + return this.call_method (this.get_core_type, null, "so", [ "WebAssembly.Core."+obj.constructor.name ]); + }, + get_wasm_type: function(obj) { + var coreType = obj[Symbol.for("wasm type")]; + if (typeof coreType === "undefined") { + coreType = this.wasm_get_core_type(obj); + if (typeof coreType !== "undefined") { + obj.constructor.prototype[Symbol.for("wasm type")] = coreType; + } + } + return coreType; + }, + // Object wrapping helper functions to handle reference handles that will + // be used in managed code. + mono_wasm_register_obj: function(obj) { + + var gc_handle = undefined; + if (obj !== null && typeof obj !== "undefined") + { + gc_handle = obj.__mono_gchandle__; + + if (typeof gc_handle === "undefined") { + var handle = this.mono_wasm_free_list.length ? + this.mono_wasm_free_list.pop() : this.mono_wasm_ref_counter++; + obj.__mono_jshandle__ = handle; + // Obtain the JS -> C# type mapping. + var wasm_type = this.get_wasm_type(obj); + gc_handle = obj.__mono_gchandle__ = this.wasm_binding_obj_new(handle + 1, wasm_type); + this.mono_wasm_object_registry[handle] = obj; + + } + } + return gc_handle; + }, + mono_wasm_require_handle: function(handle) { + if (handle > 0) + return this.mono_wasm_object_registry[handle - 1]; + return null; + }, + mono_wasm_unregister_obj: function(js_id) { + var obj = this.mono_wasm_object_registry[js_id - 1]; + if (typeof obj !== "undefined" && obj !== null) { + // if this is the global object then do not + // unregister it. + if (typeof ___mono_wasm_global___ !== "undefined" && ___mono_wasm_global___ === obj) + return obj; + + var gc_handle = obj.__mono_gchandle__; + if (typeof gc_handle !== "undefined") { + this.wasm_unbind_js_obj_and_free(js_id); + + obj.__mono_gchandle__ = undefined; + obj.__mono_jshandle__ = undefined; + + this.mono_wasm_object_registry[js_id - 1] = undefined; + this.mono_wasm_free_list.push(js_id - 1); + } + } + return obj; + }, + mono_wasm_free_handle: function(handle) { + this.mono_wasm_unregister_obj(handle); + }, + mono_wasm_free_raw_object: function(js_id) { + var obj = this.mono_wasm_object_registry[js_id - 1]; + if (typeof obj !== "undefined" && obj !== null) { + // if this is the global object then do not + // unregister it. + if (typeof ___mono_wasm_global___ !== "undefined" && ___mono_wasm_global___ === obj) + return obj; + + var gc_handle = obj.__mono_gchandle__; + if (typeof gc_handle !== "undefined") { + + obj.__mono_gchandle__ = undefined; + obj.__mono_jshandle__ = undefined; + + this.mono_wasm_object_registry[js_id - 1] = undefined; + this.mono_wasm_free_list.push(js_id - 1); + } + } + return obj; + }, + mono_wasm_get_global: function() { + function testGlobal(obj) { + obj['___mono_wasm_global___'] = obj; + var success = typeof ___mono_wasm_global___ === 'object' && obj['___mono_wasm_global___'] === obj; + if (!success) { + delete obj['___mono_wasm_global___']; + } + return success; + } + if (typeof ___mono_wasm_global___ === 'object') { + return ___mono_wasm_global___; + } + if (typeof global === 'object' && testGlobal(global)) { + ___mono_wasm_global___ = global; + } else if (typeof window === 'object' && testGlobal(window)) { + ___mono_wasm_global___ = window; + } else if (testGlobal((function(){return Function;})()('return this')())) { + + ___mono_wasm_global___ = (function(){return Function;})()('return this')(); + + } + if (typeof ___mono_wasm_global___ === 'object') { + return ___mono_wasm_global___; + } + throw Error('Unable to get mono wasm global object.'); + }, + + }, + + mono_wasm_invoke_js_with_args: function(js_handle, method_name, args, is_exception) { + BINDING.bindings_lazy_init (); + + var obj = BINDING.get_js_obj (js_handle); + if (!obj) { + setValue (is_exception, 1, "i32"); + return BINDING.js_string_to_mono_string ("Invalid JS object handle '" + js_handle + "'"); + } + + var js_name = BINDING.conv_string (method_name); + if (!js_name) { + setValue (is_exception, 1, "i32"); + return BINDING.js_string_to_mono_string ("Invalid method name object '" + method_name + "'"); + } + + var js_args = BINDING.mono_array_to_js_array(args); + + var res; + try { + var m = obj [js_name]; + if (typeof m === "undefined") + throw new Error("Method: '" + js_name + "' not found for: '" + Object.prototype.toString.call(obj) + "'"); + var res = m.apply (obj, js_args); + return BINDING.js_to_mono_obj (res); + } catch (e) { + var res = e.toString (); + setValue (is_exception, 1, "i32"); + if (res === null || res === undefined) + res = "unknown exception"; + return BINDING.js_string_to_mono_string (res); + } + }, + mono_wasm_get_object_property: function(js_handle, property_name, is_exception) { + BINDING.bindings_lazy_init (); + + var obj = BINDING.mono_wasm_require_handle (js_handle); + if (!obj) { + setValue (is_exception, 1, "i32"); + return BINDING.js_string_to_mono_string ("Invalid JS object handle '" + js_handle + "'"); + } + + var js_name = BINDING.conv_string (property_name); + if (!js_name) { + setValue (is_exception, 1, "i32"); + return BINDING.js_string_to_mono_string ("Invalid property name object '" + js_name + "'"); + } + + var res; + try { + var m = obj [js_name]; + if (m === Object(m) && obj.__is_mono_proxied__) + m.__is_mono_proxied__ = true; + + return BINDING.js_to_mono_obj (m); + } catch (e) { + var res = e.toString (); + setValue (is_exception, 1, "i32"); + if (res === null || typeof res === "undefined") + res = "unknown exception"; + return BINDING.js_string_to_mono_string (res); + } + }, + mono_wasm_set_object_property: function (js_handle, property_name, value, createIfNotExist, hasOwnProperty, is_exception) { + + BINDING.bindings_lazy_init (); + + var requireObject = BINDING.mono_wasm_require_handle (js_handle); + if (!requireObject) { + setValue (is_exception, 1, "i32"); + return BINDING.js_string_to_mono_string ("Invalid JS object handle '" + js_handle + "'"); + } + + var property = BINDING.conv_string (property_name); + if (!property) { + setValue (is_exception, 1, "i32"); + return BINDING.js_string_to_mono_string ("Invalid property name object '" + property_name + "'"); + } + + var result = false; + + var js_value = BINDING.unbox_mono_obj(value); + + if (createIfNotExist) { + requireObject[property] = js_value; + result = true; + } + else { + result = false; + if (!createIfNotExist) + { + if (!requireObject.hasOwnProperty(property)) + return false; + } + if (hasOwnProperty === true) { + if (requireObject.hasOwnProperty(property)) { + requireObject[property] = js_value; + result = true; + } + } + else { + requireObject[property] = js_value; + result = true; + } + + } + return BINDING.call_method (BINDING.box_js_bool, null, "im", [ result ]); + }, + mono_wasm_get_by_index: function(js_handle, property_index, is_exception) { + BINDING.bindings_lazy_init (); + + var obj = BINDING.mono_wasm_require_handle (js_handle); + if (!obj) { + setValue (is_exception, 1, "i32"); + return BINDING.js_string_to_mono_string ("Invalid JS object handle '" + js_handle + "'"); + } + + try { + var m = obj [property_index]; + return BINDING.js_to_mono_obj (m); + } catch (e) { + var res = e.toString (); + setValue (is_exception, 1, "i32"); + if (res === null || typeof res === "undefined") + res = "unknown exception"; + return BINDING.js_string_to_mono_string (res); + } + }, + mono_wasm_set_by_index: function(js_handle, property_index, value, is_exception) { + BINDING.bindings_lazy_init (); + + var obj = BINDING.mono_wasm_require_handle (js_handle); + if (!obj) { + setValue (is_exception, 1, "i32"); + return BINDING.js_string_to_mono_string ("Invalid JS object handle '" + js_handle + "'"); + } + + var js_value = BINDING.unbox_mono_obj(value); + + try { + obj [property_index] = js_value; + return true; + } catch (e) { + var res = e.toString (); + setValue (is_exception, 1, "i32"); + if (res === null || typeof res === "undefined") + res = "unknown exception"; + return BINDING.js_string_to_mono_string (res); + } + }, + mono_wasm_get_global_object: function(global_name, is_exception) { + BINDING.bindings_lazy_init (); + + var js_name = BINDING.conv_string (global_name); + + var globalObj = undefined; + + if (!js_name) { + globalObj = BINDING.mono_wasm_get_global(); + } + else { + globalObj = BINDING.mono_wasm_get_global()[js_name]; + } + + if (globalObj === null || typeof globalObj === undefined) { + setValue (is_exception, 1, "i32"); + return BINDING.js_string_to_mono_string ("Global object '" + js_name + "' not found."); + } + + return BINDING.js_to_mono_obj (globalObj); + }, + mono_wasm_release_handle: function(js_handle, is_exception) { + BINDING.bindings_lazy_init (); + + BINDING.mono_wasm_free_handle(js_handle); + }, + mono_wasm_release_object: function(js_handle, is_exception) { + BINDING.bindings_lazy_init (); + + BINDING.mono_wasm_free_raw_object(js_handle); + }, + mono_wasm_bind_core_object: function(js_handle, gc_handle, is_exception) { + BINDING.bindings_lazy_init (); + + var requireObject = BINDING.mono_wasm_require_handle (js_handle); + if (!requireObject) { + setValue (is_exception, 1, "i32"); + return BINDING.js_string_to_mono_string ("Invalid JS object handle '" + js_handle + "'"); + } + + BINDING.wasm_bind_core_clr_obj(js_handle, gc_handle ); + requireObject.__mono_gchandle__ = gc_handle; + return gc_handle; + }, + mono_wasm_bind_host_object: function(js_handle, gc_handle, is_exception) { + BINDING.bindings_lazy_init (); + + var requireObject = BINDING.mono_wasm_require_handle (js_handle); + if (!requireObject) { + setValue (is_exception, 1, "i32"); + return BINDING.js_string_to_mono_string ("Invalid JS object handle '" + js_handle + "'"); + } + + BINDING.wasm_bind_core_clr_obj(js_handle, gc_handle ); + requireObject.__mono_gchandle__ = gc_handle; + return gc_handle; + }, + mono_wasm_new: function (core_name, args, is_exception) { + BINDING.bindings_lazy_init (); + + var js_name = BINDING.conv_string (core_name); + + if (!js_name) { + setValue (is_exception, 1, "i32"); + return BINDING.js_string_to_mono_string ("Core object '" + js_name + "' not found."); + } + + var coreObj = BINDING.mono_wasm_get_global()[js_name]; + + if (coreObj === null || typeof coreObj === "undefined") { + setValue (is_exception, 1, "i32"); + return BINDING.js_string_to_mono_string ("JavaScript host object '" + js_name + "' not found."); + } + + var js_args = BINDING.mono_array_to_js_array(args); + + try { + + // This is all experimental !!!!!! + var allocator = function(constructor, js_args) { + // Not sure if we should be checking for anything here + var argsList = new Array(); + argsList[0] = constructor; + if (js_args) + argsList = argsList.concat(js_args); + var obj = new (constructor.bind.apply(constructor, argsList )); + return obj; + }; + + var res = allocator(coreObj, js_args); + var gc_handle = BINDING.mono_wasm_free_list.length ? BINDING.mono_wasm_free_list.pop() : BINDING.mono_wasm_ref_counter++; + BINDING.mono_wasm_object_registry[gc_handle] = res; + return BINDING.js_to_mono_obj (gc_handle + 1); + } catch (e) { + var res = e.toString (); + setValue (is_exception, 1, "i32"); + if (res === null || res === undefined) + res = "Error allocating object."; + return BINDING.js_string_to_mono_string (res); + } + + }, + mono_wasm_new_object: function(object_handle_or_function, args, is_exception) { + BINDING.bindings_lazy_init (); + + if (!object_handle_or_function) { + return BINDING.js_to_mono_obj ({}); + } + else { + + var requireObject; + if (typeof object_handle_or_function === 'function') + requireObject = object_handle_or_function; + else + requireObject = BINDING.mono_wasm_require_handle (object_handle_or_function); + + if (!requireObject) { + setValue (is_exception, 1, "i32"); + return BINDING.js_string_to_mono_string ("Invalid JS object handle '" + object_handle_or_function + "'"); + } + + var js_args = BINDING.mono_array_to_js_array(args); + + try { + + // This is all experimental !!!!!! + var allocator = function(constructor, js_args) { + // Not sure if we should be checking for anything here + var argsList = new Array(); + argsList[0] = constructor; + if (js_args) + argsList = argsList.concat(js_args); + var obj = new (constructor.bind.apply(constructor, argsList )); + return obj; + }; + + var res = allocator(requireObject, js_args); + return BINDING.extract_mono_obj (res); + } catch (e) { + var res = e.toString (); + setValue (is_exception, 1, "i32"); + if (res === null || res === undefined) + res = "Error allocating object."; + return BINDING.js_string_to_mono_string (res); + } + } + + }, + mono_wasm_typed_array_to_array: function(js_handle, is_exception) { + BINDING.bindings_lazy_init (); + + var requireObject = BINDING.mono_wasm_require_handle (js_handle); + if (!requireObject) { + setValue (is_exception, 1, "i32"); + return BINDING.js_string_to_mono_string ("Invalid JS object handle '" + js_handle + "'"); + } + + return BINDING.js_typed_array_to_array(requireObject); + }, + mono_wasm_typed_array_copy_to: function(js_handle, pinned_array, begin, end, bytes_per_element, is_exception) { + BINDING.bindings_lazy_init (); + + var requireObject = BINDING.mono_wasm_require_handle (js_handle); + if (!requireObject) { + setValue (is_exception, 1, "i32"); + return BINDING.js_string_to_mono_string ("Invalid JS object handle '" + js_handle + "'"); + } + + var res = BINDING.typedarray_copy_to(requireObject, pinned_array, begin, end, bytes_per_element); + return BINDING.js_to_mono_obj (res) + }, + mono_wasm_typed_array_from: function(pinned_array, begin, end, bytes_per_element, type, is_exception) { + BINDING.bindings_lazy_init (); + var res = BINDING.typed_array_from(pinned_array, begin, end, bytes_per_element, type); + return BINDING.js_to_mono_obj (res) + }, + mono_wasm_typed_array_copy_from: function(js_handle, pinned_array, begin, end, bytes_per_element, is_exception) { + BINDING.bindings_lazy_init (); + + var requireObject = BINDING.mono_wasm_require_handle (js_handle); + if (!requireObject) { + setValue (is_exception, 1, "i32"); + return BINDING.js_string_to_mono_string ("Invalid JS object handle '" + js_handle + "'"); + } + + var res = BINDING.typedarray_copy_from(requireObject, pinned_array, begin, end, bytes_per_element); + return BINDING.js_to_mono_obj (res) + }, + + +}; + +autoAddDeps(BindingSupportLib, '$BINDING') +mergeInto(LibraryManager.library, BindingSupportLib) diff --git a/src/mono/wasm/runtime/corebindings.c b/src/mono/wasm/runtime/corebindings.c new file mode 100644 index 0000000000000..f4700369585f9 --- /dev/null +++ b/src/mono/wasm/runtime/corebindings.c @@ -0,0 +1,179 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. +#include +#include +#include +#include +#include +#include + +#include + +//JS funcs +extern MonoObject* mono_wasm_invoke_js_with_args (int js_handle, MonoString *method, MonoArray *args, int *is_exception); +extern MonoObject* mono_wasm_get_object_property (int js_handle, MonoString *propertyName, int *is_exception); +extern MonoObject* mono_wasm_get_by_index (int js_handle, int property_index, int *is_exception); +extern MonoObject* mono_wasm_set_object_property (int js_handle, MonoString *propertyName, MonoObject *value, int createIfNotExist, int hasOwnProperty, int *is_exception); +extern MonoObject* mono_wasm_set_by_index (int js_handle, int property_index, MonoObject *value, int *is_exception); +extern MonoObject* mono_wasm_get_global_object (MonoString *global_name, int *is_exception); +extern void* mono_wasm_release_handle (int js_handle, int *is_exception); +extern void* mono_wasm_release_object (int js_handle, int *is_exception); +extern MonoObject* mono_wasm_new_object (int js_handle, MonoArray *args, int *is_exception); +extern MonoObject* mono_wasm_new (MonoString *core_name, MonoArray *args, int *is_exception); +extern int mono_wasm_bind_core_object (int js_handle, int gc_handle, int *is_exception); +extern int mono_wasm_bind_host_object (int js_handle, int gc_handle, int *is_exception); +extern MonoObject* mono_wasm_typed_array_to_array (int js_handle, int *is_exception); +extern MonoObject* mono_wasm_typed_array_copy_to (int js_handle, int ptr, int begin, int end, int bytes_per_element, int *is_exception); +extern MonoObject* mono_wasm_typed_array_from (int ptr, int begin, int end, int bytes_per_element, int type, int *is_exception); +extern MonoObject* mono_wasm_typed_array_copy_from (int js_handle, int ptr, int begin, int end, int bytes_per_element, int *is_exception); + +// Compiles a JavaScript function from the function data passed. +// Note: code snippet is not a function definition. Instead it must create and return a function instance. +EM_JS(MonoObject*, compile_function, (int snippet_ptr, int len, int *is_exception), { + try { + var data = MONO.string_decoder.decode (snippet_ptr, snippet_ptr + len); + var wrapper = '(function () { ' + data + ' })'; + var funcFactory = eval(wrapper); + var func = funcFactory(); + if (typeof func !== 'function') { + throw new Error('Code must return an instance of a JavaScript function. ' + + 'Please use `return` statement to return a function.'); + } + setValue (is_exception, 0, "i32"); + return BINDING.js_to_mono_obj (func); + } + catch (e) + { + res = e.toString (); + setValue (is_exception, 1, "i32"); + if (res === null || res === undefined) + res = "unknown exception"; + return BINDING.js_to_mono_obj (res); + } +}); + +static MonoObject* +mono_wasm_compile_function (MonoString *str, int *is_exception) +{ + if (str == NULL) + return NULL; + //char *native_val = mono_string_to_utf8 (str); + mono_unichar2 *native_val = mono_string_chars (str); + int native_len = mono_string_length (str) * 2; + + MonoObject* native_res = compile_function((int)native_val, native_len, is_exception); + mono_free (native_val); + if (native_res == NULL) + return NULL; + return native_res; +} + +void core_initialize_internals () +{ + mono_add_internal_call ("WebAssembly.Runtime::InvokeJSWithArgs", mono_wasm_invoke_js_with_args); + mono_add_internal_call ("WebAssembly.Runtime::GetObjectProperty", mono_wasm_get_object_property); + mono_add_internal_call ("WebAssembly.Runtime::GetByIndex", mono_wasm_get_by_index); + mono_add_internal_call ("WebAssembly.Runtime::SetObjectProperty", mono_wasm_set_object_property); + mono_add_internal_call ("WebAssembly.Runtime::SetByIndex", mono_wasm_set_by_index); + mono_add_internal_call ("WebAssembly.Runtime::GetGlobalObject", mono_wasm_get_global_object); + mono_add_internal_call ("WebAssembly.Runtime::ReleaseHandle", mono_wasm_release_handle); + mono_add_internal_call ("WebAssembly.Runtime::ReleaseObject", mono_wasm_release_object); + mono_add_internal_call ("WebAssembly.Runtime::NewObjectJS", mono_wasm_new_object); + mono_add_internal_call ("WebAssembly.Runtime::BindCoreObject", mono_wasm_bind_core_object); + mono_add_internal_call ("WebAssembly.Runtime::BindHostObject", mono_wasm_bind_host_object); + mono_add_internal_call ("WebAssembly.Runtime::New", mono_wasm_new); + mono_add_internal_call ("WebAssembly.Runtime::TypedArrayToArray", mono_wasm_typed_array_to_array); + mono_add_internal_call ("WebAssembly.Runtime::TypedArrayCopyTo", mono_wasm_typed_array_copy_to); + mono_add_internal_call ("WebAssembly.Runtime::TypedArrayFrom", mono_wasm_typed_array_from); + mono_add_internal_call ("WebAssembly.Runtime::TypedArrayCopyFrom", mono_wasm_typed_array_copy_from); + mono_add_internal_call ("WebAssembly.Runtime::CompileFunction", mono_wasm_compile_function); + +} + +// Int8Array | int8_t | byte or SByte (signed byte) +// Uint8Array | uint8_t | byte or Byte (unsigned byte) +// Uint8ClampedArray| uint8_t | byte or Byte (unsigned byte) +// Int16Array | int16_t | short (signed short) +// Uint16Array | uint16_t | ushort (unsigned short) +// Int32Array | int32_t | int (signed integer) +// Uint32Array | uint32_t | uint (unsigned integer) +// Float32Array | float | float +// Float64Array | double | double +// typed array marshalling +#define MARSHAL_ARRAY_BYTE 11 +#define MARSHAL_ARRAY_UBYTE 12 +#define MARSHAL_ARRAY_SHORT 13 +#define MARSHAL_ARRAY_USHORT 14 +#define MARSHAL_ARRAY_INT 15 +#define MARSHAL_ARRAY_UINT 16 +#define MARSHAL_ARRAY_FLOAT 17 +#define MARSHAL_ARRAY_DOUBLE 18 + +EMSCRIPTEN_KEEPALIVE MonoArray* +mono_wasm_typed_array_new (char *arr, int length, int size, int type) +{ + MonoClass *typeClass = mono_get_byte_class(); // default is Byte + switch (type) { + case MARSHAL_ARRAY_BYTE: + typeClass = mono_get_sbyte_class(); + break; + case MARSHAL_ARRAY_SHORT: + typeClass = mono_get_int16_class(); + break; + case MARSHAL_ARRAY_USHORT: + typeClass = mono_get_uint16_class(); + break; + case MARSHAL_ARRAY_INT: + typeClass = mono_get_int32_class(); + break; + case MARSHAL_ARRAY_UINT: + typeClass = mono_get_uint32_class(); + break; + case MARSHAL_ARRAY_FLOAT: + typeClass = mono_get_single_class(); + break; + case MARSHAL_ARRAY_DOUBLE: + typeClass = mono_get_double_class(); + break; + } + + MonoArray *buffer; + + buffer = mono_array_new (mono_get_root_domain(), typeClass, length); + memcpy(mono_array_addr_with_size(buffer, sizeof(char), 0), arr, length * size); + + return buffer; +} + +EMSCRIPTEN_KEEPALIVE int +mono_wasm_unbox_enum (MonoObject *obj) +{ + if (!obj) + return 0; + + MonoType *type = mono_class_get_type (mono_object_get_class(obj)); + + void *ptr = mono_object_unbox (obj); + switch (mono_type_get_type(mono_type_get_underlying_type (type))) { + case MONO_TYPE_I1: + case MONO_TYPE_U1: + return *(unsigned char*)ptr; + case MONO_TYPE_I2: + return *(short*)ptr; + case MONO_TYPE_U2: + return *(unsigned short*)ptr; + case MONO_TYPE_I4: + return *(int*)ptr; + case MONO_TYPE_U4: + return *(unsigned int*)ptr; + // WASM doesn't support returning longs to JS + // case MONO_TYPE_I8: + // case MONO_TYPE_U8: + default: + printf ("Invalid type %d to mono_unbox_enum\n", mono_type_get_type(mono_type_get_underlying_type (type))); + return 0; + } +} + + diff --git a/src/mono/wasm/runtime/dotnet_support.js b/src/mono/wasm/runtime/dotnet_support.js new file mode 100644 index 0000000000000..e2db502123e4a --- /dev/null +++ b/src/mono/wasm/runtime/dotnet_support.js @@ -0,0 +1,96 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +var DotNetSupportLib = { + $DOTNET: { + _dotnet_get_global: function() { + function testGlobal(obj) { + obj['___dotnet_global___'] = obj; + var success = typeof ___dotnet_global___ === 'object' && obj['___dotnet_global___'] === obj; + if (!success) { + delete obj['___dotnet_global___']; + } + return success; + } + if (typeof ___dotnet_global___ === 'object') { + return ___dotnet_global___; + } + if (typeof global === 'object' && testGlobal(global)) { + ___dotnet_global___ = global; + } else if (typeof window === 'object' && testGlobal(window)) { + ___dotnet_global___ = window; + } + if (typeof ___dotnet_global___ === 'object') { + return ___dotnet_global___; + } + throw Error('unable to get DotNet global object.'); + }, + conv_string: function (mono_obj) { + return MONO.string_decoder.copy (mono_obj); + } + }, + mono_wasm_invoke_js_marshalled: function(exceptionMessage, asyncHandleLongPtr, functionName, argsJson) { + + var mono_string = DOTNET._dotnet_get_global()._mono_string_cached + || (DOTNET._dotnet_get_global()._mono_string_cached = Module.cwrap('mono_wasm_string_from_js', 'number', ['string'])); + + try { + // Passing a .NET long into JS via Emscripten is tricky. The method here is to pass + // as pointer to the long, then combine two reads from the HEAPU32 array. + // Even though JS numbers can't represent the full range of a .NET long, it's OK + // because we'll never exceed Number.MAX_SAFE_INTEGER (2^53 - 1) in this case. + //var u32Index = $1 >> 2; + var u32Index = asyncHandleLongPtr >> 2; + var asyncHandleJsNumber = Module.HEAPU32[u32Index + 1]*4294967296 + Module.HEAPU32[u32Index]; + + // var funcNameJsString = UTF8ToString (functionName); + // var argsJsonJsString = argsJson && UTF8ToString (argsJson); + var funcNameJsString = DOTNET.conv_string(functionName); + var argsJsonJsString = argsJson && DOTNET.conv_string (argsJson); + + var dotNetExports = DOTNET._dotnet_get_global().DotNet; + if (!dotNetExports) { + throw new Error('The Microsoft.JSInterop.js library is not loaded.'); + } + + if (asyncHandleJsNumber) { + dotNetExports.jsCallDispatcher.beginInvokeJSFromDotNet(asyncHandleJsNumber, funcNameJsString, argsJsonJsString); + return 0; + } else { + var resultJson = dotNetExports.jsCallDispatcher.invokeJSFromDotNet(funcNameJsString, argsJsonJsString); + return resultJson === null ? 0 : mono_string(resultJson); + } + } catch (ex) { + var exceptionJsString = ex.message + '\n' + ex.stack; + var exceptionSystemString = mono_string(exceptionJsString); + setValue (exceptionMessage, exceptionSystemString, 'i32'); // *exceptionMessage = exceptionSystemString; + return 0; + } + }, + mono_wasm_invoke_js_unmarshalled: function(exceptionMessage, funcName, arg0, arg1, arg2) { + try { + // Get the function you're trying to invoke + var funcNameJsString = DOTNET.conv_string(funcName); + var dotNetExports = DOTNET._dotnet_get_global().DotNet; + if (!dotNetExports) { + throw new Error('The Microsoft.JSInterop.js library is not loaded.'); + } + var funcInstance = dotNetExports.jsCallDispatcher.findJSFunction(funcNameJsString); + + return funcInstance.call(null, arg0, arg1, arg2); + } catch (ex) { + var exceptionJsString = ex.message + '\n' + ex.stack; + var mono_string = Module.cwrap('mono_wasm_string_from_js', 'number', ['string']); // TODO: Cache + var exceptionSystemString = mono_string(exceptionJsString); + setValue (exceptionMessage, exceptionSystemString, 'i32'); // *exceptionMessage = exceptionSystemString; + return 0; + } + } + + +}; + +autoAddDeps(DotNetSupportLib, '$DOTNET') +mergeInto(LibraryManager.library, DotNetSupportLib) + diff --git a/src/mono/wasm/runtime/driver.c b/src/mono/wasm/runtime/driver.c new file mode 100644 index 0000000000000..c55ecba5a4c87 --- /dev/null +++ b/src/mono/wasm/runtime/driver.c @@ -0,0 +1,759 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "pinvoke-table.h" + +#ifdef CORE_BINDINGS +void core_initialize_internals (); +#endif + +// Blazor specific custom routines - see dotnet_support.js for backing code +extern void* mono_wasm_invoke_js_marshalled (MonoString **exceptionMessage, void *asyncHandleLongPtr, MonoString *funcName, MonoString *argsJson); +extern void* mono_wasm_invoke_js_unmarshalled (MonoString **exceptionMessage, MonoString *funcName, void* arg0, void* arg1, void* arg2); + +void mono_wasm_enable_debugging (int); + +void mono_ee_interp_init (const char *opts); +void mono_marshal_ilgen_init (void); +void mono_method_builder_ilgen_init (void); +void mono_sgen_mono_ilgen_init (void); +void mono_icall_table_init (void); +void mono_aot_register_module (void **aot_info); +char *monoeg_g_getenv(const char *variable); +int monoeg_g_setenv(const char *variable, const char *value, int overwrite); +void mono_free (void*); +int32_t mini_parse_debug_option (const char *option); + +static MonoClass* datetime_class; +static MonoClass* datetimeoffset_class; +static MonoClass* uri_class; + +int mono_wasm_enable_gc; + +/* Not part of public headers */ +#define MONO_ICALL_TABLE_CALLBACKS_VERSION 2 + +typedef struct { + int version; + void* (*lookup) (MonoMethod *method, char *classname, char *methodname, char *sigstart, int32_t *uses_handles); + const char* (*lookup_icall_symbol) (void* func); +} MonoIcallTableCallbacks; + +void +mono_install_icall_table_callbacks (const MonoIcallTableCallbacks *cb); + +int mono_regression_test_step (int verbose_level, char *image, char *method_name); +void mono_trace_init (void); + +#define g_new(type, size) ((type *) malloc (sizeof (type) * (size))) +#define g_new0(type, size) ((type *) calloc (sizeof (type), (size))) + +static MonoDomain *root_domain; + +static MonoString* +mono_wasm_invoke_js (MonoString *str, int *is_exception) +{ + if (str == NULL) + return NULL; + + mono_unichar2 *native_val = mono_string_chars (str); + int native_len = mono_string_length (str) * 2; + + mono_unichar2 *native_res = (mono_unichar2*)EM_ASM_INT ({ + var str = MONO.string_decoder.decode ($0, $0 + $1); + try { + var res = eval (str); + if (res === null || res == undefined) + return 0; + res = res.toString (); + setValue ($2, 0, "i32"); + } catch (e) { + res = e.toString (); + setValue ($2, 1, "i32"); + if (res === null || res === undefined) + res = "unknown exception"; + } + var buff = Module._malloc((res.length + 1) * 2); + stringToUTF16 (res, buff, (res.length + 1) * 2); + return buff; + }, (int)native_val, native_len, is_exception); + + if (native_res == NULL) + return NULL; + + MonoString *res = mono_string_from_utf16 (native_res); + free (native_res); + return res; +} + +static void +wasm_logger (const char *log_domain, const char *log_level, const char *message, mono_bool fatal, void *user_data) +{ + if (fatal) { + EM_ASM( + var err = new Error(); + console.log ("Stacktrace: \n"); + console.log (err.stack); + ); + + fprintf (stderr, "%s\n", message); + fflush (stderr); + + abort (); + } else { + fprintf (stdout, "L: %s\n", message); + } +} + +#ifdef DRIVER_GEN +#include "driver-gen.c" +#endif + +typedef struct WasmAssembly_ WasmAssembly; + +struct WasmAssembly_ { + MonoBundledAssembly assembly; + WasmAssembly *next; +}; + +static WasmAssembly *assemblies; +static int assembly_count; + +EMSCRIPTEN_KEEPALIVE void +mono_wasm_add_assembly (const char *name, const unsigned char *data, unsigned int size) +{ + int len = strlen (name); + if (!strcasecmp (".pdb", &name [len - 4])) { + char *new_name = strdup (name); + //FIXME handle debugging assemblies with .exe extension + strcpy (&new_name [len - 3], "dll"); + mono_register_symfile_for_assembly (new_name, data, size); + return; + } + WasmAssembly *entry = g_new0 (WasmAssembly, 1); + entry->assembly.name = strdup (name); + entry->assembly.data = data; + entry->assembly.size = size; + entry->next = assemblies; + assemblies = entry; + ++assembly_count; +} + +EMSCRIPTEN_KEEPALIVE void +mono_wasm_setenv (const char *name, const char *value) +{ + monoeg_g_setenv (strdup (name), strdup (value), 1); +} + +#ifdef ENABLE_NETCORE +static void *sysglobal_native_handle; +#endif + +static void* +wasm_dl_load (const char *name, int flags, char **err, void *user_data) +{ + for (int i = 0; i < sizeof (pinvoke_tables) / sizeof (void*); ++i) { + if (!strcmp (name, pinvoke_names [i])) + return pinvoke_tables [i]; + } + +#ifdef ENABLE_NETCORE + if (!strcmp (name, "System.Globalization.Native")) + return sysglobal_native_handle; +#endif + +#if WASM_SUPPORTS_DLOPEN + return dlopen(name, flags); +#endif + + return NULL; +} + +static mono_bool +wasm_dl_is_pinvoke_tables (void* handle) +{ + for (int i = 0; i < sizeof (pinvoke_tables) / sizeof (void*); ++i) { + if (pinvoke_tables [i] == handle) { + return 1; + } + } + return 0; +} + +static void* +wasm_dl_symbol (void *handle, const char *name, char **err, void *user_data) +{ +#ifdef ENABLE_NETCORE + if (handle == sysglobal_native_handle) + assert (0); +#endif + +#if WASM_SUPPORTS_DLOPEN + if (!wasm_dl_is_pinvoke_tables (handle)) { + return dlsym (handle, name); + } +#endif + + PinvokeImport *table = (PinvokeImport*)handle; + for (int i = 0; table [i].name; ++i) { + if (!strcmp (table [i].name, name)) + return table [i].func; + } + return NULL; +} + +#ifdef ENABLE_NETCORE +/* Missing System.Native symbols */ +int SystemNative_CloseNetworkChangeListenerSocket (int a) { return 0; } +int SystemNative_CreateNetworkChangeListenerSocket (int a) { return 0; } +void SystemNative_ReadEvents (int a,int b) {} +int SystemNative_SchedGetAffinity (int a,int b) { return 0; } +int SystemNative_SchedSetAffinity (int a,int b) { return 0; } +#endif + +#if !defined(ENABLE_AOT) || defined(EE_MODE_LLVMONLY_INTERP) +#define NEED_INTERP 1 +#ifndef LINK_ICALLS +// FIXME: llvm+interp mode needs this to call icalls +#define NEED_NORMAL_ICALL_TABLES 1 +#endif +#endif + +#ifdef LINK_ICALLS + +#include "icall-table.h" + +static int +compare_int (const void *k1, const void *k2) +{ + return *(int*)k1 - *(int*)k2; +} + +static void* +icall_table_lookup (MonoMethod *method, char *classname, char *methodname, char *sigstart, int32_t *uses_handles) +{ + uint32_t token = mono_method_get_token (method); + assert (token); + assert ((token & MONO_TOKEN_METHOD_DEF) == MONO_TOKEN_METHOD_DEF); + uint32_t token_idx = token - MONO_TOKEN_METHOD_DEF; + + int *indexes = NULL; + int indexes_size = 0; + uint8_t *handles = NULL; + void **funcs = NULL; + + *uses_handles = 0; + + const char *image_name = mono_image_get_name (mono_class_get_image (mono_method_get_class (method))); + +#ifdef ICALL_TABLE_mscorlib + if (!strcmp (image_name, "mscorlib") || !strcmp (image_name, "System.Private.CoreLib")) { + indexes = mscorlib_icall_indexes; + indexes_size = sizeof (mscorlib_icall_indexes) / 4; + handles = mscorlib_icall_handles; + funcs = mscorlib_icall_funcs; + assert (sizeof (mscorlib_icall_indexes [0]) == 4); + } +#ifdef ICALL_TABLE_System + if (!strcmp (image_name, "System")) { + indexes = System_icall_indexes; + indexes_size = sizeof (System_icall_indexes) / 4; + handles = System_icall_handles; + funcs = System_icall_funcs; + } +#endif + assert (indexes); + + void *p = bsearch (&token_idx, indexes, indexes_size, 4, compare_int); + if (!p) { + return NULL; + printf ("wasm: Unable to lookup icall: %s\n", mono_method_get_name (method)); + exit (1); + } + + uint32_t idx = (int*)p - indexes; + *uses_handles = handles [idx]; + + //printf ("ICALL: %s %x %d %d\n", methodname, token, idx, (int)(funcs [idx])); + + return funcs [idx]; +#endif +} + +static const char* +icall_table_lookup_symbol (void *func) +{ + assert (0); + return NULL; +} + +#endif + +void mono_initialize_internals () +{ + mono_add_internal_call ("WebAssembly.Runtime::InvokeJS", mono_wasm_invoke_js); + // TODO: what happens when two types in different assemblies have the same FQN? + + // Blazor specific custom routines - see dotnet_support.js for backing code + mono_add_internal_call ("WebAssembly.JSInterop.InternalCalls::InvokeJSMarshalled", mono_wasm_invoke_js_marshalled); + mono_add_internal_call ("WebAssembly.JSInterop.InternalCalls::InvokeJSUnmarshalled", mono_wasm_invoke_js_unmarshalled); + +#ifdef CORE_BINDINGS + core_initialize_internals(); +#endif + +} + +EMSCRIPTEN_KEEPALIVE void +mono_wasm_load_runtime (const char *managed_path, int enable_debugging) +{ + const char *interp_opts = ""; + + monoeg_g_setenv ("MONO_LOG_LEVEL", "debug", 0); + monoeg_g_setenv ("MONO_LOG_MASK", "gc", 0); +#ifdef ENABLE_NETCORE + monoeg_g_setenv ("DOTNET_SYSTEM_GLOBALIZATION_INVARIANT", "1", 0); +#endif + + mini_parse_debug_option ("top-runtime-invoke-unhandled"); + + mono_dl_fallback_register (wasm_dl_load, wasm_dl_symbol, NULL, NULL); + +#ifdef ENABLE_AOT + // Defined in driver-gen.c + register_aot_modules (); +#ifdef EE_MODE_LLVMONLY_INTERP + mono_jit_set_aot_mode (MONO_AOT_MODE_LLVMONLY_INTERP); +#else + mono_jit_set_aot_mode (MONO_AOT_MODE_LLVMONLY); +#endif +#else + mono_jit_set_aot_mode (MONO_AOT_MODE_INTERP_LLVMONLY); + if (enable_debugging) { + // Disable optimizations which interfere with debugging + interp_opts = "-all"; + mono_wasm_enable_debugging (enable_debugging); + } +#endif + +#ifdef LINK_ICALLS + /* Link in our own linked icall table */ + static const MonoIcallTableCallbacks mono_icall_table_callbacks = + { + MONO_ICALL_TABLE_CALLBACKS_VERSION, + icall_table_lookup, + icall_table_lookup_symbol + }; + mono_install_icall_table_callbacks (&mono_icall_table_callbacks); +#endif + +#ifdef NEED_NORMAL_ICALL_TABLES + mono_icall_table_init (); +#endif +#ifdef NEED_INTERP + mono_ee_interp_init (interp_opts); + mono_marshal_ilgen_init (); + mono_method_builder_ilgen_init (); + mono_sgen_mono_ilgen_init (); +#endif + + if (assembly_count) { + MonoBundledAssembly **bundle_array = g_new0 (MonoBundledAssembly*, assembly_count + 1); + WasmAssembly *cur = assemblies; + int i = 0; + while (cur) { + bundle_array [i] = &cur->assembly; + cur = cur->next; + ++i; + } + mono_register_bundled_assemblies ((const MonoBundledAssembly **)bundle_array); + } + + mono_trace_init (); + mono_trace_set_log_handler (wasm_logger, NULL); + root_domain = mono_jit_init_version ("mono", "v4.0.30319"); + + mono_initialize_internals(); + + mono_thread_set_main (mono_thread_current ()); +} + +EMSCRIPTEN_KEEPALIVE MonoAssembly* +mono_wasm_assembly_load (const char *name) +{ + MonoImageOpenStatus status; + MonoAssemblyName* aname = mono_assembly_name_new (name); + if (!name) + return NULL; + + MonoAssembly *res = mono_assembly_load (aname, NULL, &status); + mono_assembly_name_free (aname); + + return res; +} + +EMSCRIPTEN_KEEPALIVE MonoClass* +mono_wasm_assembly_find_class (MonoAssembly *assembly, const char *namespace, const char *name) +{ + return mono_class_from_name (mono_assembly_get_image (assembly), namespace, name); +} + +EMSCRIPTEN_KEEPALIVE MonoMethod* +mono_wasm_assembly_find_method (MonoClass *klass, const char *name, int arguments) +{ + return mono_class_get_method_from_name (klass, name, arguments); +} + +EMSCRIPTEN_KEEPALIVE MonoObject* +mono_wasm_invoke_method (MonoMethod *method, MonoObject *this_arg, void *params[], MonoObject **out_exc) +{ + MonoObject *exc = NULL; + MonoObject *res; + + if (out_exc) + *out_exc = NULL; + res = mono_runtime_invoke (method, this_arg, params, &exc); + if (exc) { + if (out_exc) + *out_exc = exc; + + MonoObject *exc2 = NULL; + res = (MonoObject*)mono_object_to_string (exc, &exc2); + if (exc2) + res = (MonoObject*) mono_string_new (root_domain, "Exception Double Fault"); + return res; + } + + MonoMethodSignature *sig = mono_method_signature (method); + MonoType *type = mono_signature_get_return_type (sig); + // If the method return type is void return null + // This gets around a memory access crash when the result return a value when + // a void method is invoked. + if (mono_type_get_type (type) == MONO_TYPE_VOID) + return NULL; + + return res; +} + +EMSCRIPTEN_KEEPALIVE MonoMethod* +mono_wasm_assembly_get_entry_point (MonoAssembly *assembly) +{ + MonoImage *image; + MonoMethod *method; + + image = mono_assembly_get_image (assembly); + uint32_t entry = mono_image_get_entry_point (image); + if (!entry) + return NULL; + + return mono_get_method (image, entry, NULL); +} + +EMSCRIPTEN_KEEPALIVE char * +mono_wasm_string_get_utf8 (MonoString *str) +{ + return mono_string_to_utf8 (str); //XXX JS is responsible for freeing this +} + +EMSCRIPTEN_KEEPALIVE void +mono_wasm_string_convert (MonoString *str) +{ + if (str == NULL) + return; + + mono_unichar2 *native_val = mono_string_chars (str); + int native_len = mono_string_length (str) * 2; + + EM_ASM ({ + MONO.string_decoder.decode($0, $0 + $1, true); + }, (int)native_val, native_len); +} + +EMSCRIPTEN_KEEPALIVE MonoString * +mono_wasm_string_from_js (const char *str) +{ + if (str) + return mono_string_new (root_domain, str); + else + return NULL; +} + +static int +class_is_task (MonoClass *klass) +{ + if (!strcmp ("System.Threading.Tasks", mono_class_get_namespace (klass)) && + (!strcmp ("Task", mono_class_get_name (klass)) || !strcmp ("Task`1", mono_class_get_name (klass)))) + return 1; + + return 0; +} + +MonoClass* mono_get_uri_class(MonoException** exc) +{ + MonoAssembly* assembly = mono_wasm_assembly_load ("System"); + if (!assembly) + return NULL; + MonoClass* klass = mono_wasm_assembly_find_class(assembly, "System", "Uri"); + return klass; +} + +#define MARSHAL_TYPE_INT 1 +#define MARSHAL_TYPE_FP 2 +#define MARSHAL_TYPE_STRING 3 +#define MARSHAL_TYPE_VT 4 +#define MARSHAL_TYPE_DELEGATE 5 +#define MARSHAL_TYPE_TASK 6 +#define MARSHAL_TYPE_OBJECT 7 +#define MARSHAL_TYPE_BOOL 8 +#define MARSHAL_TYPE_ENUM 9 +#define MARSHAL_TYPE_DATE 20 +#define MARSHAL_TYPE_DATEOFFSET 21 +#define MARSHAL_TYPE_URI 22 + +// typed array marshalling +#define MARSHAL_ARRAY_BYTE 11 +#define MARSHAL_ARRAY_UBYTE 12 +#define MARSHAL_ARRAY_SHORT 13 +#define MARSHAL_ARRAY_USHORT 14 +#define MARSHAL_ARRAY_INT 15 +#define MARSHAL_ARRAY_UINT 16 +#define MARSHAL_ARRAY_FLOAT 17 +#define MARSHAL_ARRAY_DOUBLE 18 + +EMSCRIPTEN_KEEPALIVE int +mono_wasm_get_obj_type (MonoObject *obj) +{ + if (!obj) + return 0; + + if (!datetime_class) + datetime_class = mono_class_from_name (mono_get_corlib(), "System", "DateTime"); + if (!datetimeoffset_class) + datetimeoffset_class = mono_class_from_name (mono_get_corlib(), "System", "DateTimeOffset"); + if (!uri_class) { + MonoException** exc = NULL; + uri_class = mono_get_uri_class(exc); + } + + MonoClass *klass = mono_object_get_class (obj); + MonoType *type = mono_class_get_type (klass); + + switch (mono_type_get_type (type)) { + // case MONO_TYPE_CHAR: prob should be done not as a number? + case MONO_TYPE_BOOLEAN: + return MARSHAL_TYPE_BOOL; + case MONO_TYPE_I1: + case MONO_TYPE_U1: + case MONO_TYPE_I2: + case MONO_TYPE_U2: + case MONO_TYPE_I4: + case MONO_TYPE_U4: + case MONO_TYPE_I8: + case MONO_TYPE_U8: + case MONO_TYPE_I: // IntPtr + return MARSHAL_TYPE_INT; + case MONO_TYPE_R4: + case MONO_TYPE_R8: + return MARSHAL_TYPE_FP; + case MONO_TYPE_STRING: + return MARSHAL_TYPE_STRING; + case MONO_TYPE_SZARRAY: { // simple zero based one-dim-array + MonoClass *eklass = mono_class_get_element_class(klass); + MonoType *etype = mono_class_get_type (eklass); + + switch (mono_type_get_type (etype)) { + case MONO_TYPE_U1: + return MARSHAL_ARRAY_UBYTE; + case MONO_TYPE_I1: + return MARSHAL_ARRAY_BYTE; + case MONO_TYPE_U2: + return MARSHAL_ARRAY_USHORT; + case MONO_TYPE_I2: + return MARSHAL_ARRAY_SHORT; + case MONO_TYPE_U4: + return MARSHAL_ARRAY_UINT; + case MONO_TYPE_I4: + return MARSHAL_ARRAY_INT; + case MONO_TYPE_R4: + return MARSHAL_ARRAY_FLOAT; + case MONO_TYPE_R8: + return MARSHAL_ARRAY_DOUBLE; + default: + return MARSHAL_TYPE_OBJECT; + } + } + default: + if (klass == datetime_class) + return MARSHAL_TYPE_DATE; + if (klass == datetimeoffset_class) + return MARSHAL_TYPE_DATEOFFSET; + if (uri_class && mono_class_is_assignable_from(uri_class, klass)) + return MARSHAL_TYPE_URI; + if (mono_class_is_enum (klass)) + return MARSHAL_TYPE_ENUM; + if (!mono_type_is_reference (type)) //vt + return MARSHAL_TYPE_VT; + if (mono_class_is_delegate (klass)) + return MARSHAL_TYPE_DELEGATE; + if (class_is_task(klass)) + return MARSHAL_TYPE_TASK; + + return MARSHAL_TYPE_OBJECT; + } +} + +EMSCRIPTEN_KEEPALIVE int +mono_unbox_int (MonoObject *obj) +{ + if (!obj) + return 0; + MonoType *type = mono_class_get_type (mono_object_get_class(obj)); + + void *ptr = mono_object_unbox (obj); + switch (mono_type_get_type (type)) { + case MONO_TYPE_I1: + case MONO_TYPE_BOOLEAN: + return *(signed char*)ptr; + case MONO_TYPE_U1: + return *(unsigned char*)ptr; + case MONO_TYPE_I2: + return *(short*)ptr; + case MONO_TYPE_U2: + return *(unsigned short*)ptr; + case MONO_TYPE_I4: + case MONO_TYPE_I: + return *(int*)ptr; + case MONO_TYPE_U4: + return *(unsigned int*)ptr; + // WASM doesn't support returning longs to JS + // case MONO_TYPE_I8: + // case MONO_TYPE_U8: + default: + printf ("Invalid type %d to mono_unbox_int\n", mono_type_get_type (type)); + return 0; + } +} + +EMSCRIPTEN_KEEPALIVE double +mono_wasm_unbox_float (MonoObject *obj) +{ + if (!obj) + return 0; + MonoType *type = mono_class_get_type (mono_object_get_class(obj)); + + void *ptr = mono_object_unbox (obj); + switch (mono_type_get_type (type)) { + case MONO_TYPE_R4: + return *(float*)ptr; + case MONO_TYPE_R8: + return *(double*)ptr; + default: + printf ("Invalid type %d to mono_wasm_unbox_float\n", mono_type_get_type (type)); + return 0; + } +} + +EMSCRIPTEN_KEEPALIVE int +mono_wasm_array_length (MonoArray *array) +{ + return mono_array_length (array); +} + +EMSCRIPTEN_KEEPALIVE MonoObject* +mono_wasm_array_get (MonoArray *array, int idx) +{ + return mono_array_get (array, MonoObject*, idx); +} + +EMSCRIPTEN_KEEPALIVE MonoArray* +mono_wasm_obj_array_new (int size) +{ + return mono_array_new (root_domain, mono_get_object_class (), size); +} + +EMSCRIPTEN_KEEPALIVE void +mono_wasm_obj_array_set (MonoArray *array, int idx, MonoObject *obj) +{ + mono_array_setref (array, idx, obj); +} + +EMSCRIPTEN_KEEPALIVE MonoArray* +mono_wasm_string_array_new (int size) +{ + return mono_array_new (root_domain, mono_get_string_class (), size); +} + +EMSCRIPTEN_KEEPALIVE int +mono_wasm_exec_regression (int verbose_level, char *image) +{ + return mono_regression_test_step (verbose_level, image, NULL) ? 0 : 1; +} + +EMSCRIPTEN_KEEPALIVE int +mono_wasm_exit (int exit_code) +{ + exit (exit_code); +} + +EMSCRIPTEN_KEEPALIVE void +mono_wasm_set_main_args (int argc, char* argv[]) +{ + mono_runtime_set_main_args (argc, argv); +} + +EMSCRIPTEN_KEEPALIVE int +mono_wasm_strdup (const char *s) +{ + return (int)strdup (s); +} + +EMSCRIPTEN_KEEPALIVE void +mono_wasm_parse_runtime_options (int argc, char* argv[]) +{ + mono_jit_parse_options (argc, argv); +} + +EMSCRIPTEN_KEEPALIVE void +mono_wasm_enable_on_demand_gc (void) +{ + mono_wasm_enable_gc = 1; +} + +// Returns the local timezone default is UTC. +EM_JS(size_t, mono_wasm_timezone_get_local_name, (), +{ + var res = "UTC"; + try { + res = Intl.DateTimeFormat().resolvedOptions().timeZone; + } catch(e) {} + + var buff = Module._malloc((res.length + 1) * 2); + stringToUTF16 (res, buff, (res.length + 1) * 2); + return buff; +}) + +void +mono_timezone_get_local_name (MonoString **result) +{ + // WASM returns back an int pointer to a string UTF16 buffer. + // We then cast to `mono_unichar2*`. Returning `mono_unichar2*` from the JavaScript call will + // result in cast warnings from the compiler. + mono_unichar2 *tzd_local_name = (mono_unichar2*)mono_wasm_timezone_get_local_name (); + *result = mono_string_from_utf16 (tzd_local_name); + free (tzd_local_name); +} diff --git a/src/mono/wasm/runtime/library_mono.js b/src/mono/wasm/runtime/library_mono.js new file mode 100644 index 0000000000000..61e6234949d50 --- /dev/null +++ b/src/mono/wasm/runtime/library_mono.js @@ -0,0 +1,1000 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +var MonoSupportLib = { + $MONO__postset: 'MONO.export_functions (Module);', + $MONO: { + pump_count: 0, + timeout_queue: [], + _vt_stack: [], + mono_wasm_runtime_is_ready : false, + mono_wasm_ignore_pdb_load_errors: true, + pump_message: function () { + if (!this.mono_background_exec) + this.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; + this.mono_background_exec (); + } + }, + + export_functions: function (module) { + module ["pump_message"] = MONO.pump_message; + module ["mono_load_runtime_and_bcl"] = MONO.mono_load_runtime_and_bcl; + }, + + mono_text_decoder: undefined, + string_decoder: { + copy: function (mono_string) { + if (mono_string == 0) + return null; + + if (!this.mono_wasm_string_convert) + this.mono_wasm_string_convert = Module.cwrap ("mono_wasm_string_convert", null, ['number']); + + this.mono_wasm_string_convert (mono_string); + var result = this.result; + this.result = undefined; + return result; + }, + decode: function (start, end, save) { + if (!MONO.mono_text_decoder) { + MONO.mono_text_decoder = typeof TextDecoder !== 'undefined' ? new TextDecoder('utf-16le') : undefined; + } + + 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. + 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_get_call_stack: function() { + if (!this.mono_wasm_current_bp_id) + this.mono_wasm_current_bp_id = Module.cwrap ("mono_wasm_current_bp_id", 'number'); + if (!this.mono_wasm_enum_frames) + this.mono_wasm_enum_frames = Module.cwrap ("mono_wasm_enum_frames", null); + + var bp_id = this.mono_wasm_current_bp_id (); + this.active_frames = []; + this.mono_wasm_enum_frames (); + + var the_frames = this.active_frames; + this.active_frames = []; + return { + "breakpoint_id": bp_id, + "frames": the_frames, + }; + }, + + _fixup_name_value_objects: function (var_list) { + var out_list = []; + + var _fixup_value = function (value) { + if (value != null && value != undefined) { + var descr = value.description; + if (descr == null || descr == undefined) + value.description = '' + value.value; + } + return value; + }; + + var i = 0; + while (i < var_list.length) { + var o = var_list [i]; + var name = o.name; + if (name == null || name == undefined) { + i ++; + o.value = _fixup_value(o.value); + out_list.push (o); + continue; + } + + if (i + 1 < var_list.length) + o.value = _fixup_value(var_list[i + 1].value); + + out_list.push (o); + i += 2; + } + + return out_list; + }, + + _filter_automatic_properties: function (props) { + var names_found = {}; + var final_var_list = []; + + for (var i in props) { + var p = props [i]; + if (p.name in names_found) + continue; + + if (p.name.endsWith ("k__BackingField")) + p.name = p.name.replace ("k__BackingField", "") + .replace ('<', '') + .replace ('>', ''); + + names_found [p.name] = p.name; + final_var_list.push (p); + } + + return final_var_list; + }, + + mono_wasm_get_variables: function(scope, var_list) { + if (!this.mono_wasm_get_var_info) + this.mono_wasm_get_var_info = Module.cwrap ("mono_wasm_get_var_info", null, [ 'number', 'number', 'number']); + + this.var_info = []; + var numBytes = var_list.length * Int32Array.BYTES_PER_ELEMENT; + var ptr = Module._malloc(numBytes); + var heapBytes = new Int32Array(Module.HEAP32.buffer, ptr, numBytes); + for (let i=0; i') > 0) + res [i].name = name.substring (1, name.indexOf ('>')); + } + + if (this._async_method_objectId != 0) { + for (let i in res) { + if (res [i].value.isValueType != undefined && res [i].value.isValueType) + res [i].value.objectId = `dotnet:valuetype:${this._async_method_objectId}:${res [i].fieldOffset}`; + } + } + + this._post_process_details(res); + this.var_info = [] + + return res; + }, + + mono_wasm_get_object_properties: function(objId, expandValueTypes) { + if (!this.mono_wasm_get_object_properties_info) + this.mono_wasm_get_object_properties_info = Module.cwrap ("mono_wasm_get_object_properties", null, [ 'number', 'bool' ]); + + this.var_info = []; + this.mono_wasm_get_object_properties_info (objId, expandValueTypes); + + var res = MONO._filter_automatic_properties (MONO._fixup_name_value_objects (this.var_info)); + for (var i = 0; i < res.length; i++) { + if (res [i].value.isValueType != undefined && res [i].value.isValueType) + res [i].value.objectId = `dotnet:valuetype:${objId}:${res [i].fieldOffset}`; + } + + this.var_info = []; + + return res; + }, + + mono_wasm_get_array_values: function(objId) { + if (!this.mono_wasm_get_array_values_info) + this.mono_wasm_get_array_values_info = Module.cwrap ("mono_wasm_get_array_values", null, [ 'number' ]); + + this.var_info = []; + this.mono_wasm_get_array_values_info (objId); + + var res = MONO._fixup_name_value_objects (this.var_info); + for (var i = 0; i < res.length; i++) { + if (res [i].value.isValueType != undefined && res [i].value.isValueType) + res [i].value.objectId = `dotnet:array:${objId}:${i}`; + } + + this.var_info = []; + + return res; + }, + + mono_wasm_get_array_value_expanded: function(objId, idx) { + if (!this.mono_wasm_get_array_value_expanded_info) + this.mono_wasm_get_array_value_expanded_info = Module.cwrap ("mono_wasm_get_array_value_expanded", null, [ 'number', 'number' ]); + + this.var_info = []; + this.mono_wasm_get_array_value_expanded_info (objId, idx); + + var res = MONO._fixup_name_value_objects (this.var_info); + // length should be exactly one! + if (res [0].value.isValueType != undefined && res [0].value.isValueType) + res [0].value.objectId = `dotnet:array:${objId}:${idx}`; + + this.var_info = []; + + return res; + }, + + _post_process_details: function (details) { + if (details == undefined) + return {}; + + if (details.length > 0) + this._extract_and_cache_value_types(details); + + return details; + }, + + _next_value_type_id: function () { + return ++this._next_value_type_id_var; + }, + + _extract_and_cache_value_types: function (var_list) { + if (var_list == undefined || !Array.isArray (var_list) || var_list.length == 0) + return var_list; + + for (let i in var_list) { + var value = var_list [i].value; + if (value == undefined || value.type != "object") + continue; + + if (value.isValueType != true || value.expanded != true) // undefined would also give us false + continue; + + var objectId = value.objectId; + if (objectId == undefined) + objectId = `dotnet:valuetype:${this._next_value_type_id ()}`; + value.objectId = objectId; + + this._extract_and_cache_value_types (value.members); + + this._value_types_cache [objectId] = value.members; + delete value.members; + } + + return var_list; + }, + + _get_details_for_value_type: function (objectId, fetchDetailsFn) { + if (objectId in this._value_types_cache) + return this._value_types_cache[objectId]; + + this._post_process_details (fetchDetailsFn()); + if (objectId in this._value_types_cache) + return this._value_types_cache[objectId]; + + // return error + throw new Error (`Could not get details for ${objectId}`); + }, + + _is_object_id_array: function (objectId) { + // Keep this in sync with `_get_array_details` + return (objectId.startsWith ('dotnet:array:') && objectId.split (':').length == 3); + }, + + _get_array_details: function (objectId, objectIdParts) { + // Keep this in sync with `_is_object_id_array` + switch (objectIdParts.length) { + case 3: + return this._post_process_details (this.mono_wasm_get_array_values(objectIdParts[2])); + + case 4: + var arrayObjectId = objectIdParts[2]; + var arrayIdx = objectIdParts[3]; + return this._get_details_for_value_type( + objectId, () => this.mono_wasm_get_array_value_expanded(arrayObjectId, arrayIdx)); + + default: + throw new Error (`object id format not supported : ${objectId}`); + } + }, + + mono_wasm_get_details: function (objectId, args) { + var parts = objectId.split(":"); + if (parts[0] != "dotnet") + throw new Error ("Can't handle non-dotnet object ids. ObjectId: " + objectId); + + switch (parts[1]) { + case "object": + if (parts.length != 3) + throw new Error(`exception this time: Invalid object id format: ${objectId}`); + + return this._post_process_details(this.mono_wasm_get_object_properties(parts[2], false)); + + case "array": + return this._get_array_details(objectId, parts); + + case "valuetype": + if (parts.length != 3 && parts.length != 4) { + // dotnet:valuetype:vtid + // dotnet:valuetype:containerObjectId:vtId + throw new Error(`Invalid object id format: ${objectId}`); + } + + var containerObjectId = parts[2]; + return this._get_details_for_value_type(objectId, () => this.mono_wasm_get_object_properties(containerObjectId, true)); + + case "cfo_res": { + if (!(objectId in this._call_function_res_cache)) + throw new Error(`Could not find any object with id ${objectId}`); + + var real_obj = this._call_function_res_cache [objectId]; + if (args.accessorPropertiesOnly) { + // var val_accessors = JSON.stringify ([ + // { + // name: "__proto__", + // get: { type: "function", className: "Function", description: "function get __proto__ () {}", objectId: "dotnet:cfo_res:9999" }, + // set: { type: "function", className: "Function", description: "function set __proto__ () {}", objectId: "dotnet:cfo_res:8888" }, + // isOwn: false + // }], undefined, 4); + return { __value_as_json_string__: "[]" }; + } + + // behaving as if (args.ownProperties == true) + var descriptors = Object.getOwnPropertyDescriptors (real_obj); + var own_properties = []; + Object.keys (descriptors).forEach (k => { + var new_obj; + var 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 { + // 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) + }; + } + + own_properties.push (new_obj); + }); + + return { __value_as_json_string__: JSON.stringify (own_properties) }; + } + + default: + throw new Error(`Unknown object id format: ${objectId}`); + } + }, + + _cache_call_function_res: function (obj) { + var id = `dotnet:cfo_res:${this._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]; + }, + + mono_wasm_call_function_on: function (request) { + var objId = request.objectId; + var proxy; + + if (objId in this._call_function_res_cache) { + proxy = this._call_function_res_cache [objId]; + } else if (!objId.startsWith ('dotnet:cfo_res:')) { + var details = this.mono_wasm_get_details(objId); + var target_is_array = this._is_object_id_array (objId); + proxy = target_is_array ? [] : {}; + + Object.keys(details).forEach(p => { + var prop = details[p]; + if (target_is_array) { + proxy.push(prop.value); + } else { + if (prop.name != undefined) + proxy [prop.name] = prop.value; + else // when can this happen?? + proxy[''+p] = prop.value; + } + }); + } + + var fn_args = request.arguments != undefined ? request.arguments.map(a => a.value) : []; + var fn_eval_str = `var fn = ${request.functionDeclaration}; fn.call (proxy, ...[${fn_args}]);`; + + var fn_res = eval (fn_eval_str); + if (request.returnByValue) + return fn_res; + + if (fn_res == undefined) + throw Error ('Function returned undefined result'); + + var fn_res_id = this._cache_call_function_res (fn_res); + if (Object.getPrototypeOf (fn_res) == Array.prototype) { + return { + type: "object", + subtype: "array", + className: "Array", + description: `Array(${fn_res.length})`, + objectId: fn_res_id + }; + } else { + return { type: "object", className: "Object", description: "Object", objectId: fn_res_id }; + } + }, + + mono_wasm_start_single_stepping: function (kind) { + console.log (">> mono_wasm_start_single_stepping " + kind); + if (!this.mono_wasm_setup_single_step) + this.mono_wasm_setup_single_step = Module.cwrap ("mono_wasm_setup_single_step", 'number', [ 'number']); + + this._next_value_type_id_var = 0; + this._value_types_cache = {}; + + return this.mono_wasm_setup_single_step (kind); + }, + + mono_wasm_runtime_ready: function () { + this.mono_wasm_runtime_is_ready = true; + // DO NOT REMOVE - magic debugger init function + console.debug ("mono_wasm_runtime_ready", "fe00e07a-5519-4dfe-b35a-f867dbaf2e28"); + + this._next_value_type_id_var = 0; + this._value_types_cache = {}; + + // FIXME: where should this go? + this._next_call_function_res_id = 0; + this._call_function_res_cache = {}; + }, + + mono_wasm_set_breakpoint: function (assembly, method_token, il_offset) { + if (!this.mono_wasm_set_bp) + this.mono_wasm_set_bp = Module.cwrap ('mono_wasm_set_breakpoint', 'number', ['string', 'number', 'number']); + + return this.mono_wasm_set_bp (assembly, method_token, il_offset) + }, + + mono_wasm_remove_breakpoint: function (breakpoint_id) { + if (!this.mono_wasm_del_bp) + this.mono_wasm_del_bp = Module.cwrap ('mono_wasm_remove_breakpoint', 'number', ['number']); + + return this.mono_wasm_del_bp (breakpoint_id); + }, + + // 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']); + 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 = 'WebAssembly.Runtime::StopProfile'; + if (!('send_to' in options)) + options.send_to = 'WebAssembly.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]); + }, + + mono_load_runtime_and_bcl: function (vfs_prefix, deploy_prefix, enable_debugging, file_list, loaded_cb, fetch_file_cb) { + var pending = file_list.length; + var loaded_files = []; + var mono_wasm_add_assembly = Module.cwrap ('mono_wasm_add_assembly', null, ['string', 'number', 'number']); + + if (!fetch_file_cb) { + if (ENVIRONMENT_IS_NODE) { + var fs = require('fs'); + fetch_file_cb = function (asset) { + console.log("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 { + fetch_file_cb = function (asset) { + return fetch (asset, { credentials: 'same-origin' }); + } + } + } + + file_list.forEach (function(file_name) { + + var fetch_promise = fetch_file_cb (locateFile(deploy_prefix + "/" + file_name)); + + fetch_promise.then (function (response) { + if (!response.ok) { + // If it's a 404 on a .pdb, we don't want to block the app from starting up. + // We'll just skip that file and continue (though the 404 is logged in the console). + if (response.status === 404 && file_name.match(/\.pdb$/) && MONO.mono_wasm_ignore_pdb_load_errors) { + --pending; + throw "MONO-WASM: Skipping failed load for .pdb file: '" + file_name + "'"; + } + else { + throw "MONO_WASM: Failed to load file: '" + file_name + "'"; + } + } + else { + loaded_files.push (response.url); + return response ['arrayBuffer'] (); + } + }).then (function (blob) { + var asm = new Uint8Array (blob); + var memory = Module._malloc(asm.length); + var heapBytes = new Uint8Array(Module.HEAPU8.buffer, memory, asm.length); + heapBytes.set (asm); + mono_wasm_add_assembly (file_name, memory, asm.length); + + //console.log ("MONO_WASM: Loaded: " + file_name); + --pending; + if (pending == 0) { + MONO.loaded_files = loaded_files; + var load_runtime = Module.cwrap ('mono_wasm_load_runtime', null, ['string', 'number']); + + console.log ("MONO_WASM: Initializing mono runtime"); + if (ENVIRONMENT_IS_SHELL || ENVIRONMENT_IS_NODE) { + try { + load_runtime (vfs_prefix, enable_debugging); + } catch (ex) { + print ("MONO_WASM: load_runtime () failed: " + ex); + var err = new Error(); + print ("MONO_WASM: Stacktrace: \n"); + print (err.stack); + + var wasm_exit = Module.cwrap ('mono_wasm_exit', null, ['number']); + wasm_exit (1); + } + } else { + load_runtime (vfs_prefix, enable_debugging); + } + MONO.mono_wasm_runtime_ready (); + loaded_cb (); + } + }); + }); + }, + + mono_wasm_get_loaded_files: function() { + console.log(">>>mono_wasm_get_loaded_files"); + return this.loaded_files; + }, + + mono_wasm_clear_all_breakpoints: function() { + if (!this.mono_clear_bps) + this.mono_clear_bps = Module.cwrap ('mono_wasm_clear_all_breakpoints', null); + + this.mono_clear_bps (); + }, + + mono_wasm_add_null_var: function(className) + { + fixed_class_name = MONO._mono_csharp_fixup_class_name(Module.UTF8ToString (className)); + if (!fixed_class_name) { + // Eg, when a @className is passed from js itself, like + // mono_wasm_add_null_var ("string") + fixed_class_name = className; + } + MONO.var_info.push ({value: { + type: "object", + className: fixed_class_name, + description: fixed_class_name, + subtype: "null" + }}); + }, + + _mono_wasm_add_string_var: function(var_value) { + if (var_value == 0) { + MONO.mono_wasm_add_null_var ("string"); + return; + } + + MONO.var_info.push({ + value: { + type: "string", + value: var_value, + } + }); + }, + + _mono_wasm_add_getter_var: function(className) { + fixed_class_name = MONO._mono_csharp_fixup_class_name (className); + var value = `${fixed_class_name} { get; }`; + MONO.var_info.push({ + value: { + type: "symbol", + value: value, + description: value + } + }); + }, + + _mono_wasm_add_array_var: function(className, objectId, length) { + fixed_class_name = MONO._mono_csharp_fixup_class_name(className); + if (objectId == 0) { + MONO.mono_wasm_add_null_var (fixed_class_name); + return; + } + + MONO.var_info.push({ + value: { + type: "object", + subtype: "array", + className: fixed_class_name, + description: `${fixed_class_name}(${length})`, + objectId: "dotnet:array:"+ objectId, + } + }); + }, + + mono_wasm_add_typed_value: function (type, str_value, value) { + var type_str = type; + if (typeof type != 'string') + type_str = Module.UTF8ToString (type); + if (typeof str_value != 'string') + str_value = Module.UTF8ToString (str_value); + + switch (type_str) { + case "bool": + MONO.var_info.push ({ + value: { + type: "boolean", + value: value != 0 + } + }); + break; + + case "char": + MONO.var_info.push ({ + value: { + type: "symbol", + value: `${value} '${String.fromCharCode (value)}'` + } + }); + break; + + case "number": + MONO.var_info.push ({ + value: { + type: "number", + value: value + } + }); + break; + + case "string": + MONO._mono_wasm_add_string_var (str_value); + break; + + case "getter": + MONO._mono_wasm_add_getter_var (str_value); + break; + + case "array": + MONO._mono_wasm_add_array_var (str_value, value.objectId, value.length); + break; + + case "pointer": { + MONO.var_info.push ({ + value: { + type: "symbol", + value: str_value, + description: str_value + } + }); + } + break; + + default: { + var msg = `'${str_value}' ${value}`; + + MONO.var_info.push ({ + value: { + type: "symbol", + value: msg, + description: msg + } + }); + break; + } + } + }, + + _mono_csharp_fixup_class_name: function(className) + { + // Fix up generic names like Foo`2 to Foo + // and nested class names like Foo/Bar to Foo.Bar + return className.replace(/\//g, '.').replace(/`\d+/g, ''); + }, + }, + + mono_wasm_add_typed_value: function (type, str_value, value) { + MONO.mono_wasm_add_typed_value (type, str_value, value); + }, + + mono_wasm_add_properties_var: function(name, field_offset) { + MONO.var_info.push({ + name: Module.UTF8ToString (name), + fieldOffset: field_offset + }); + }, + + mono_wasm_set_is_async_method: function(objectId) { + MONO._async_method_objectId = objectId; + }, + + mono_wasm_begin_value_type_var: function(className, toString) { + fixed_class_name = MONO._mono_csharp_fixup_class_name(Module.UTF8ToString (className)); + var vt_obj = { + value: { + type: "object", + className: fixed_class_name, + description: (toString == 0 ? fixed_class_name : Module.UTF8ToString (toString)), + // objectId will be generated by MonoProxy + expanded: true, + isValueType: true, + members: [] + } + }; + if (MONO._vt_stack.length == 0) + MONO._old_var_info = MONO.var_info; + + MONO.var_info = vt_obj.value.members; + MONO._vt_stack.push (vt_obj); + }, + + mono_wasm_end_value_type_var: function() { + var top_vt_obj_popped = MONO._vt_stack.pop (); + top_vt_obj_popped.value.members = MONO._filter_automatic_properties ( + MONO._fixup_name_value_objects (top_vt_obj_popped.value.members)); + + if (MONO._vt_stack.length == 0) { + MONO.var_info = MONO._old_var_info; + MONO.var_info.push(top_vt_obj_popped); + } else { + var top_obj = MONO._vt_stack [MONO._vt_stack.length - 1]; + top_obj.value.members.push (top_vt_obj_popped); + MONO.var_info = top_obj.value.members; + } + }, + + mono_wasm_add_value_type_unexpanded_var: function (className, toString) { + fixed_class_name = MONO._mono_csharp_fixup_class_name(Module.UTF8ToString (className)); + MONO.var_info.push({ + value: { + type: "object", + className: fixed_class_name, + description: (toString == 0 ? fixed_class_name : Module.UTF8ToString (toString)), + // objectId added when enumerating object's properties + expanded: false, + isValueType: true + } + }); + }, + + mono_wasm_add_enum_var: function(className, members, value) { + // FIXME: flags + // + + // group0: Monday:0 + // group1: Monday + // group2: 0 + var re = new RegExp (`[,]?([^,:]+):(${value}(?=,)|${value}$)`, 'g') + var members_str = Module.UTF8ToString (members); + + var match = re.exec(members_str); + var member_name = match == null ? ('' + value) : match [1]; + + fixed_class_name = MONO._mono_csharp_fixup_class_name(Module.UTF8ToString (className)); + MONO.var_info.push({ + value: { + type: "object", + className: fixed_class_name, + description: member_name, + isEnum: true + } + }); + }, + + mono_wasm_add_array_item: function(position) { + MONO.var_info.push({ + name: `${position}` + }); + }, + + mono_wasm_add_obj_var: function(className, toString, objectId) { + if (objectId == 0) { + MONO.mono_wasm_add_null_var (className); + return; + } + + fixed_class_name = MONO._mono_csharp_fixup_class_name(Module.UTF8ToString (className)); + MONO.var_info.push({ + value: { + type: "object", + className: fixed_class_name, + description: (toString == 0 ? fixed_class_name : Module.UTF8ToString (toString)), + objectId: "dotnet:object:"+ objectId, + } + }); + }, + + /* + * @className, and @targetName are in the following format: + * + * :[]: + */ + mono_wasm_add_func_var: function (className, targetName, objectId) { + if (objectId == 0) { + MONO.mono_wasm_add_null_var ( + MONO._mono_csharp_fixup_class_name (Module.UTF8ToString (className))); + return; + } + + function args_to_sig (args_str) { + var parts = args_str.split (":"); + // TODO: min length = 3? + parts = parts.map (a => MONO._mono_csharp_fixup_class_name (a)); + + // method name at the end + var method_name = parts.pop (); + + // ret type at the beginning + var ret_sig = parts [0]; + var args_sig = parts.splice (1).join (', '); + return `${ret_sig} ${method_name} (${args_sig})`; + } + + var tgt_sig; + if (targetName != 0) + tgt_sig = args_to_sig (Module.UTF8ToString (targetName)); + + var type_name = MONO._mono_csharp_fixup_class_name (Module.UTF8ToString (className)); + + if (objectId == -1) { + // Target property + MONO.var_info.push ({ + value: { + type: "symbol", + value: tgt_sig, + description: tgt_sig, + } + }); + } else { + MONO.var_info.push ({ + value: { + type: "object", + className: type_name, + description: tgt_sig, + objectId: "dotnet:object:" + objectId, + } + }); + } + }, + + mono_wasm_add_frame: function(il, method, assembly_name, method_full_name) { + var parts = Module.UTF8ToString (method_full_name).split (":", 2); + MONO.active_frames.push( { + il_pos: il, + method_token: method, + assembly_name: Module.UTF8ToString (assembly_name), + // Extract just the method name from `{class_name}:{method_name}` + method_name: parts [parts.length - 1] + }); + }, + + schedule_background_exec: function () { + ++MONO.pump_count; + if (ENVIRONMENT_IS_WEB) { + window.setTimeout (MONO.pump_message, 0); + } else if (ENVIRONMENT_IS_WORKER) { + self.setTimeout (MONO.pump_message, 0); + } else if (ENVIRONMENT_IS_NODE) { + global.setTimeout (MONO.pump_message, 0); + } + }, + + mono_set_timeout: function (timeout, id) { + if (!this.mono_set_timeout_exec) + this.mono_set_timeout_exec = Module.cwrap ("mono_set_timeout_exec", null, [ 'number' ]); + if (ENVIRONMENT_IS_WEB) { + window.setTimeout (function () { + this.mono_set_timeout_exec (id); + }, timeout); + } else if (ENVIRONMENT_IS_WORKER) { + self.setTimeout (function () { + this.mono_set_timeout_exec (id); + }, timeout); + } else if (ENVIRONMENT_IS_NODE) { + global.setTimeout (function () { + global.mono_set_timeout_exec (id); + }, timeout); + } else { + ++MONO.pump_count; + MONO.timeout_queue.push(function() { + this.mono_set_timeout_exec (id); + }) + } + }, + + mono_wasm_fire_bp: function () { + console.log ("mono_wasm_fire_bp"); + debugger; + } +}; + +autoAddDeps(MonoSupportLib, '$MONO') +mergeInto(LibraryManager.library, MonoSupportLib) diff --git a/src/mono/wasm/runtime/linker-preserves.xml b/src/mono/wasm/runtime/linker-preserves.xml new file mode 100644 index 0000000000000..2e46279b8793a --- /dev/null +++ b/src/mono/wasm/runtime/linker-preserves.xml @@ -0,0 +1,7 @@ + + + + + + + diff --git a/src/mono/wasm/wasm.proj b/src/mono/wasm/wasm.proj new file mode 100644 index 0000000000000..3ea7167a8c5c3 --- /dev/null +++ b/src/mono/wasm/wasm.proj @@ -0,0 +1,46 @@ + + + $(NetCoreAppCurrent) + + + + + + $(MonoObjDir)wasm/pinvoke-table.h + $(ArtifactsDir)bin\lib-runtime-packs\runtimes\browser-wasm\lib\$(NetCoreAppCurrent) + + + + + + + + + + + + + + + + + + + + + + + + + + + + +