diff --git a/eng/testing/tests.wasm.targets b/eng/testing/tests.wasm.targets
index 273ab76ef90a3f..d017b726c32772 100644
--- a/eng/testing/tests.wasm.targets
+++ b/eng/testing/tests.wasm.targets
@@ -163,6 +163,11 @@
<_WasmVFSFilesToCopy Include="@(WasmFilesToIncludeInFileSystem)" />
<_WasmVFSFilesToCopy TargetPath="%(FileName)%(Extension)" Condition="'%(TargetPath)' == ''" />
+
+ <_WasmItemsToPass Include="@(WasmMarshaledType)" OriginalItemName__="WasmMarshaledType" />
+
+
+
+
+
+
+
diff --git a/src/libraries/System.Private.Runtime.InteropServices.JavaScript/src/ILLink/ILLink.Descriptors.xml b/src/libraries/System.Private.Runtime.InteropServices.JavaScript/src/ILLink/ILLink.Descriptors.xml
new file mode 100644
index 00000000000000..2b22b327ed2468
--- /dev/null
+++ b/src/libraries/System.Private.Runtime.InteropServices.JavaScript/src/ILLink/ILLink.Descriptors.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
diff --git a/src/libraries/System.Private.Runtime.InteropServices.JavaScript/src/System.Private.Runtime.InteropServices.JavaScript.csproj b/src/libraries/System.Private.Runtime.InteropServices.JavaScript/src/System.Private.Runtime.InteropServices.JavaScript.csproj
index e6991801ba5c99..afe692c9f46b6b 100644
--- a/src/libraries/System.Private.Runtime.InteropServices.JavaScript/src/System.Private.Runtime.InteropServices.JavaScript.csproj
+++ b/src/libraries/System.Private.Runtime.InteropServices.JavaScript/src/System.Private.Runtime.InteropServices.JavaScript.csproj
@@ -7,6 +7,7 @@
+
@@ -31,6 +32,10 @@
+
+
+
+
diff --git a/src/libraries/System.Private.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Codegen.cs b/src/libraries/System.Private.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Codegen.cs
new file mode 100644
index 00000000000000..fa0f54299b73a7
--- /dev/null
+++ b/src/libraries/System.Private.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Codegen.cs
@@ -0,0 +1,486 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Text;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Diagnostics.CodeAnalysis;
+using System.Reflection;
+using System.Threading.Tasks;
+
+namespace System.Runtime.InteropServices.JavaScript
+{
+ public static unsafe class Codegen
+ {
+ public static readonly int PointerSize = sizeof(IntPtr);
+ // HACK: Unless we align all the argument values in the heap by this amount,
+ // certain parameter types will be garbled when received by target C# functions
+ public const int IndirectAddressAlignment = 8;
+
+ private static readonly Dictionary FastUnboxHandlers = new Dictionary {
+ { MarshalType.INT, "getI32(unboxBuffer)" },
+ { MarshalType.POINTER, "getU32(unboxBuffer)" }, // FIXME: Is this right?
+ { MarshalType.UINT32, "getU32(unboxBuffer)" },
+ { MarshalType.FP32, "getF32(unboxBuffer)" },
+ { MarshalType.FP64, "getF64(unboxBuffer)" },
+ { MarshalType.BOOL, "getI32(unboxBuffer) !== 0" },
+ { MarshalType.CHAR, "String.fromCharCode(getI32(unboxBuffer))" },
+ };
+
+ public abstract class BuilderStateBase {
+ public MarshalString MarshalString;
+ public StringBuilder Output = new StringBuilder();
+ public HashSet ClosureReferences = new HashSet();
+ }
+
+ public class MarshalBuilderState : BuilderStateBase {
+ public HashSet<(string, int)> TypeReferences = new HashSet<(string, int)>();
+ public StringBuilder Phase2 = new StringBuilder();
+ public Dictionary Closure = new Dictionary();
+ public int ArgIndex, RootIndex, DirectOffset, IndirectOffset;
+
+ public string ArgKey => $"arg{ArgIndex}";
+
+ public MarshalBuilderState () {
+ ClosureReferences = new HashSet {
+ "_malloc",
+ "_error",
+ };
+ }
+ }
+
+ public class BoundMethodBuilderState : BuilderStateBase {
+ public string? FriendlyName;
+ public MethodInfo Method;
+
+ public BoundMethodBuilderState (MethodInfo method) {
+ Method = method;
+ ClosureReferences = new HashSet {
+ "_error",
+ "mono_wasm_new_root",
+ "_create_temp_frame",
+ "_get_args_root_buffer_for_method_call",
+ "_get_buffer_for_method_call",
+ "_handle_exception_for_call",
+ "_teardown_after_call",
+ "mono_wasm_try_unbox_primitive_and_get_type",
+ "_unbox_mono_obj_root_with_known_nonprimitive_type",
+ "invoke_method",
+ "getI32",
+ "getU32",
+ "getF32",
+ "getF64",
+ };
+ }
+ }
+
+ private static string ToJsBool (bool b) => b ? "true" : "false";
+
+ public static void GenerateSignatureConverter (MarshalBuilderState state) {
+ int length = state.MarshalString.ArgumentCount;
+ var debugName = string.Concat("converter_", state.MarshalString.Key);
+ var variadicName = string.Concat("varConverter_", state.MarshalString.Key);
+
+ // First we generate the individual steps that pack each argument into the buffer and
+ // place pointers to each argument into the args list that is passed when invoking a method.
+ var output = state.Output;
+ for (int i = 0; i < length; i++) {
+ state.ArgIndex = i;
+ var ch = state.MarshalString[i];
+ EmitMarshalStep(state, ch);
+ }
+
+ // Now we capture that list of steps so we can put stuff above it. Generating the list of
+ // steps produced valuable information like how large our buffer needs to be.
+ var temp = output.ToString();
+ output.Clear();
+
+ // This special comment assigns a URL to this generated function in browser debuggers
+ output.AppendLine($"//# sourceURL=https://mono-wasm.invalid/signature/{state.MarshalString.Key}");
+ output.AppendLine("\"use strict\";");
+
+ var alignmentMinusOne = IndirectAddressAlignment - 1;
+ // HACK: We have to pad out both buffers to ensure that all addresses will have an alignment of 8
+ // If we don't do this, passing values to C# functions can fail (typically for doubles)
+ var directSize = (state.DirectOffset + alignmentMinusOne) / IndirectAddressAlignment * IndirectAddressAlignment;
+ var indirectSize = (state.IndirectOffset + alignmentMinusOne) / IndirectAddressAlignment * IndirectAddressAlignment;
+ var totalBufferSize = directSize + indirectSize + IndirectAddressAlignment;
+ output.AppendLine($"// '{state.MarshalString.Signature}' {length} argument(s)");
+ output.AppendLine($"// direct buffer {state.DirectOffset} byte(s), indirect {state.IndirectOffset} byte(s)");
+
+ if (length > 0) {
+ // Now we scan through all the closure references that were generated while emitting
+ // the marshal steps, and pull them out of the closure table into local variables in
+ // the scope of the outer function. This will make them visible to the two inner
+ // inner functions we're generating (which are the actual signature converter + its
+ // variadic wrapper), eliminating any need to do table lookups on every invocation.
+ // FIXME: It's possible to end up with a cyclic dependency between converters this way
+
+ // TODO: Sort this for consistent code
+ foreach (var key in state.ClosureReferences)
+ output.AppendLine($"const {key} = get_api('{key}');");
+ foreach (var tup in state.TypeReferences)
+ output.AppendLine($"const {tup.Item1} = get_type_converter({tup.Item2});");
+ }
+
+ output.AppendLine("");
+ output.Append($"function {debugName} (buffer, rootBuffer, methodPtr");
+ for (int i = 0; i < length; i++)
+ output.Append($", arg{i}");
+ output.AppendLine(") {");
+
+ if (length > 0) {
+ output.AppendLine(" if (!methodPtr) _error('no method provided');");
+ if (state.RootIndex > 0)
+ state.Output.AppendLine($" if (!rootBuffer) _error('no root buffer provided');");
+ // When a signature converter is called it may be passed an existing buffer for reuse, but
+ // if not it will allocate one on the fly. The caller is responsible for freeing it.
+ output.AppendLine($" if (!buffer) buffer = _malloc({totalBufferSize});");
+ // FIXME: While we're aligning the size of the direct buffer, it's possible 'buffer' itself is not
+ // properly aligned, which would mean indirectBuffer will also not be properly aligned.
+ // In my testing emscripten's malloc always produces aligned addresses, but we may want to
+ // detect and handle this by shifting indirectBuffer forward to align it.
+ output.AppendLine($" const directBuffer = buffer, indirectBuffer = directBuffer + {directSize};");
+ output.AppendLine(temp);
+
+ // Some marshaling operations need to occur in two phases, so we append the second phase
+ // code right at the end before returning
+ if (state.Phase2.Length > 0)
+ output.AppendLine(state.Phase2.ToString());
+
+ output.AppendLine(" return buffer;");
+ } else {
+ output.AppendLine(" return 0;");
+ }
+ output.AppendLine("};");
+
+ // Generate a small dispatcher function that will unpack an arguments array to pass
+ // the individual arguments to the signature converter. This is much slower than
+ // taking arguments directly so it is only available as a fallback
+ output.AppendLine("");
+ output.AppendLine($"function {variadicName} (buffer, rootBuffer, methodPtr, args) {{");
+ output.AppendLine($" if (args.length !== {length}) _error('Expected {length} argument(s)');");
+ if (length > 0) {
+ output.Append($" return {debugName}(buffer, rootBuffer, methodPtr");
+ for (int i = 0; i < length; i++)
+ output.Append($", args[{i}]");
+ output.AppendLine(");");
+ } else {
+ output.Append(" return 0;");
+ }
+ output.AppendLine("};");
+
+ var pMethod = state.MarshalString.Method?.MethodHandle.Value ?? IntPtr.Zero;
+ var method = state.MarshalString.ContainsAuto
+ ? pMethod.ToInt32().ToString()
+ : "null";
+
+ // At the end our wrapper function returns the two nested closures along with information
+ // on the signature they're for, so that the JS bindings layer can store everything away
+ // and do relevant setup (allocating the correct sized buffer, etc.)
+ output.AppendLine("");
+ output.AppendLine("return {");
+ output.AppendLine($" arg_count: {length}, ");
+ output.AppendLine($" args_marshal: '{state.MarshalString.Signature}', ");
+ output.AppendLine($" compiled_function: {debugName}, ");
+ output.AppendLine($" compiled_variadic_function: {variadicName}, ");
+ output.AppendLine($" contains_auto: {ToJsBool(state.MarshalString.ContainsAuto)}, ");
+ output.AppendLine($" is_result_definitely_unmarshaled: {ToJsBool(state.MarshalString.RawReturnValue)}, ");
+ output.AppendLine($" method: {method}, ");
+ output.AppendLine($" name: '{state.MarshalString.Key}', ");
+ output.AppendLine($" needs_root_buffer: {ToJsBool(state.RootIndex > 0)}, ");
+ output.AppendLine($" root_buffer_size: {state.RootIndex}, ");
+ output.AppendLine($" scratchBuffer: 0, ");
+ output.AppendLine($" scratchRootBuffer: null, ");
+ output.AppendLine($" size: {totalBufferSize}, ");
+ output.AppendLine("};");
+ }
+
+ public static void EmitPrimitiveMarshalStep (MarshalBuilderState state, string setterName) {
+ state.ClosureReferences.Add(setterName);
+ state.ClosureReferences.Add("setU32");
+ var offsetKey = $"offset{state.ArgIndex}";
+ state.Output.AppendLine($" let {offsetKey} = indirectBuffer + {state.IndirectOffset};");
+ state.Output.AppendLine($" {setterName}({offsetKey}, {state.ArgKey});");
+ state.Output.AppendLine($" setU32(directBuffer + {state.DirectOffset}, {offsetKey});");
+ state.IndirectOffset += IndirectAddressAlignment;
+ state.DirectOffset += PointerSize;
+ }
+
+ public static void EmitRawPointerMarshalStep (MarshalBuilderState state) {
+ state.ClosureReferences.Add("setU32");
+ state.Output.AppendLine($" setU32(directBuffer + {state.DirectOffset}, {state.ArgKey});");
+ state.DirectOffset += PointerSize;
+ }
+
+ public static void EmitManagedMarshalStep (MarshalBuilderState state, string? converter) {
+ state.ClosureReferences.Add("setU32");
+
+ var key = state.ArgKey;
+ if (converter != null) {
+ key = $"converted{state.ArgIndex}";
+ // Converters can either be a bare function name or raw 'foo(x, ..., y)' JS, where we will replace the '...'
+ var parenIndex = converter.IndexOf('(');
+ if (parenIndex >= 0) {
+ state.ClosureReferences.Add(converter.Substring(0, parenIndex));
+ state.Output.AppendLine($" const {key} = {converter.Replace("...", state.ArgKey)};");
+ } else {
+ state.ClosureReferences.Add(converter);
+ state.Output.AppendLine($" const {key} = {converter}({state.ArgKey});");
+ }
+ }
+
+ state.Output.AppendLine($" rootBuffer.set({state.RootIndex}, {key});");
+ state.Output.AppendLine($" setU32(directBuffer + {state.DirectOffset}, {key});");
+ state.RootIndex += 1;
+ state.DirectOffset += PointerSize;
+ }
+
+ private static void EmitCustomMarshalStep (MarshalBuilderState state, Type argType) {
+ state.ClosureReferences.Add("setU32");
+ var typePtr = argType.TypeHandle.Value;
+ var converterKey = $"type{typePtr.ToInt32()}";
+ state.TypeReferences.Add((converterKey, typePtr.ToInt32()));
+
+ var callArgs = $"{state.ArgKey}, methodPtr, {state.ArgIndex}";
+ state.Output.AppendLine($" rootBuffer.set({state.RootIndex}, {converterKey}({callArgs}));");
+
+ if (argType.IsValueType) {
+ state.ClosureReferences.Add("mono_wasm_unbox_rooted");
+ var unboxedKey = $"unboxed{state.ArgIndex}";
+ // HACK: We need to do all these unboxes last after all the transform steps have run,
+ // because invoking a converter or creating a string instance could cause a GC and move
+ // the rooted object to a new location, invalidating the unbox_rooted return value.
+ state.Phase2.AppendLine($" const {unboxedKey} = mono_wasm_unbox_rooted(rootBuffer.get({state.RootIndex}));");
+ state.Phase2.AppendLine($" setU32(directBuffer + {state.DirectOffset}, {unboxedKey});");
+ } else {
+ // Note that even though we aren't unboxing, we still read the object address back from
+ // the root buffer, because the conversion steps may have caused a GC and moved the
+ // object after we initially created it.
+ state.Phase2.AppendLine($" setU32(directBuffer + {state.DirectOffset}, rootBuffer.get({state.RootIndex}));");
+ }
+
+ state.RootIndex += 1;
+ state.DirectOffset += PointerSize;
+ }
+
+ public static void EmitMarshalStep (MarshalBuilderState state, ArgsMarshalCharacter ch) {
+ // If this slot in the signature uses the Auto type ('a'), we need to select an
+ // appropriate type for the parameter based on the target method's type info
+ if (ch == ArgsMarshalCharacter.Auto) {
+ var method = state.MarshalString.Method;
+ if (method == null)
+ // This either means no method was provided, or we failed to resolve a method
+ // from the method handle we were provided (this can happen if it's generic)
+ throw new Exception("No method provided when compiling converter");
+ var parms = method.GetParameters();
+ if (state.ArgIndex >= parms.Length)
+ throw new Exception($"Too many signature characters ({state.MarshalString.ArgumentCount}) for method ({parms.Length} args)");
+
+ var parm = parms[state.ArgIndex];
+ var pName = string.IsNullOrEmpty(parm.Name)
+ ? $"#{state.ArgIndex}"
+ : parm.Name;
+ var argType = parm.ParameterType;
+ var autoMarshalType = Runtime.GetMarshalTypeFromType(argType);
+
+ state.Output.AppendLine($"// #{state.ArgIndex} Auto {argType} {pName} -> {autoMarshalType}");
+
+ switch (autoMarshalType) {
+ // For basic types, we can just select an appropriate MarshalType for them, and then
+ // use the corresponding signature character as a replacement for the one we're missing
+ default:
+ ch = (ArgsMarshalCharacter)(int)Runtime.GetCallSignatureCharacterForMarshalType(autoMarshalType, null);
+ break;
+ // If the marshal type selector produced bare ValueType or Object, it needs custom marshaling
+ case MarshalType.VT:
+ EmitCustomMarshalStep(state, argType);
+ return;
+ case MarshalType.OBJECT:
+ // Though if it's just bare 'object', we cannot identify the marshaler at compile time here,
+ // and we need to let the regular js_to_mono_obj path below run to do it at run time.
+ if (argType != typeof(object)) {
+ EmitCustomMarshalStep(state, argType);
+ return;
+ } else {
+ ch = ArgsMarshalCharacter.JSObj;
+ break;
+ }
+ }
+ } else {
+ state.Output.AppendLine($"// #{state.ArgIndex} {ch}");
+ }
+
+ switch (ch) {
+ case ArgsMarshalCharacter.Int32:
+ EmitPrimitiveMarshalStep(state, "setI32");
+ return;
+ case ArgsMarshalCharacter.Int64:
+ EmitPrimitiveMarshalStep(state, "setI64");
+ return;
+ case ArgsMarshalCharacter.Float32:
+ EmitPrimitiveMarshalStep(state, "setF32");
+ return;
+ case ArgsMarshalCharacter.Float64:
+ EmitPrimitiveMarshalStep(state, "setF64");
+ return;
+ case ArgsMarshalCharacter.ByteSpan:
+ EmitPrimitiveMarshalStep(state, "_setSpan");
+ return;
+ case ArgsMarshalCharacter.MONOObj:
+ EmitRawPointerMarshalStep(state);
+ return;
+ case ArgsMarshalCharacter.String:
+ EmitManagedMarshalStep(state, "js_string_to_mono_string");
+ return;
+ case ArgsMarshalCharacter.InternedString:
+ EmitManagedMarshalStep(state, "js_string_to_mono_string_interned");
+ return;
+ case ArgsMarshalCharacter.Int32Enum:
+ state.Output.AppendLine($" if (typeof({state.ArgKey}) !== 'number') _error(`Expected numeric value for enum argument, got '${{{state.ArgKey}}}'`);");
+ EmitPrimitiveMarshalStep(state, "setI32");
+ return;
+ case ArgsMarshalCharacter.JSObj:
+ EmitManagedMarshalStep(state, "_js_to_mono_obj(false, ...)");
+ return;
+ case ArgsMarshalCharacter.Uri:
+ EmitManagedMarshalStep(state, "_js_to_mono_uri(false, ...)");
+ return;
+ case ArgsMarshalCharacter.Auto:
+ state.Output.AppendLine($" _error('Automatic type selection failed');");
+ return;
+ default:
+ throw new NotImplementedException(ch.ToString());
+ }
+ }
+
+ private static void GenerateFastUnboxCase (BoundMethodBuilderState state, MarshalType type, string? expression) {
+ var output = state.Output;
+ output.AppendLine($" case {(int)type}:");
+ output.AppendLine($" return {expression};");
+ }
+
+ private static void GenerateFastUnboxBlock (BoundMethodBuilderState state) {
+ var output = state.Output;
+ var methodReturnType = Runtime.GetMarshalTypeFromType(state.Method.ReturnType);
+ bool hasPrimitiveType = FastUnboxHandlers.TryGetValue(methodReturnType, out string? fastHandler);
+ // For the common scenario where the return type is a primitive, we want to try and unbox it directly
+ // into our existing heap allocation and then read it out of the heap. Doing this all in one operation
+ // means that we only need to enter a gc safe region twice (instead of 3+ times with the normal,
+ // slower check-type-and-then-unbox flow which has extra checks since unbox verifies the type).
+ if (!hasPrimitiveType) {
+ output.AppendLine(" if (resultRoot.value === 0)");
+ output.AppendLine(" return undefined;");
+ }
+ output.AppendLine( " let resultType = mono_wasm_try_unbox_primitive_and_get_type(resultRoot.value, unboxBuffer, unboxBufferSize);");
+ output.AppendLine( " switch (resultType) {");
+ // If we know the return type of this method and it's a primitive, we only need to generate the unbox handler for that type
+ if (hasPrimitiveType) {
+ GenerateFastUnboxCase(state, methodReturnType, fastHandler);
+ // This default case should never be hit, but the runtime is returning a boxed object so it's possible if something horrible happens
+ output.AppendLine( " default:");
+ output.AppendLine($" throw new Error('expected method return value to be of type {methodReturnType} but it was ' + resultType);");
+ } else {
+ // The return type is something we can't fast-unbox or is unknown (i.e. object)
+ foreach (var kvp in FastUnboxHandlers)
+ GenerateFastUnboxCase(state, kvp.Key, kvp.Value);
+ output.AppendLine( " default:");
+ output.AppendLine( " return _unbox_mono_obj_root_with_known_nonprimitive_type(resultRoot, resultType, unboxBuffer);");
+ }
+ output.AppendLine( " }");
+ }
+
+ public static void GenerateBoundMethod (BoundMethodBuilderState state) {
+ // input arguments:
+ // get_api, token
+
+ int length = state.MarshalString.ArgumentCount;
+ var handle = state.Method.MethodHandle.Value;
+ var name = state.FriendlyName ?? $"clr_{handle.ToInt32()}";
+ var output = state.Output;
+
+ // This special comment assigns a URL to this generated function in browser debuggers
+ output.AppendLine($"//# sourceURL=https://mono-wasm.invalid/bound_method/{handle.ToInt32()}");
+ output.AppendLine("\"use strict\";");
+ output.AppendLine($"//{state.Method?.DeclaringType?.FullName}::{state.Method?.Name}");
+
+ // Unpack various closure values into locals in the outer function that returns the actual
+ // bound method, so that the property lookup doesn't have to occur on every call
+ output.AppendLine("const method = token.method;");
+ output.AppendLine("const converter = token.converter;");
+ output.AppendLine($"const converter_{state.MarshalString.Key} = converter.compiled_function;");
+ output.AppendLine("const unboxBuffer = token.unboxBuffer;");
+ output.AppendLine("const unboxBufferSize = token.unboxBufferSize;");
+ // get_api here will also ensure that every function we reference is available and do
+ // the check now at construction time instead of later when the bound method is called
+ foreach (var key in state.ClosureReferences)
+ output.AppendLine($"const {key} = get_api('{key}');");
+
+ output.Append($"function {name} (");
+ for (int i = 0; i < length; i++) {
+ if (i < (length - 1))
+ output.Append($"arg{i}, ");
+ else
+ output.AppendLine($"arg{i}) {{");
+ }
+ if (length == 0)
+ output.AppendLine(") {");
+
+ output.AppendLine(" _create_temp_frame();");
+ output.AppendLine(" let resultRoot = token.scratchResultRoot;");
+ output.AppendLine(" let exceptionRoot = token.scratchExceptionRoot;");
+ output.AppendLine(" token.scratchResultRoot = null;");
+ output.AppendLine(" token.scratchExceptionRoot = null;");
+ output.AppendLine(" if (resultRoot === null)");
+ output.AppendLine(" resultRoot = mono_wasm_new_root();");
+ output.AppendLine(" if (exceptionRoot === null)");
+ output.AppendLine(" exceptionRoot = mono_wasm_new_root();");
+ output.AppendLine();
+
+ output.AppendLine( " let argsRootBuffer = _get_args_root_buffer_for_method_call(converter, token);");
+ output.AppendLine( " let scratchBuffer = _get_buffer_for_method_call(converter, token);");
+ output.AppendLine( " let buffer = 0;");
+ output.AppendLine( " let abnormalExit = true;");
+ output.AppendLine( " try {");
+ output.AppendLine($" buffer = converter_{state.MarshalString.Key}(");
+ output.AppendLine( " scratchBuffer, argsRootBuffer, method,");
+ for (int i = 0; i < length; i++) {
+ if (i < (length - 1))
+ output.AppendLine($" arg{i},");
+ else
+ output.AppendLine($" arg{i}");
+ }
+ output.AppendLine(" );");
+ output.AppendLine();
+
+ output.AppendLine(" resultRoot.value = invoke_method(method, 0, buffer, exceptionRoot.get_address());");
+ output.AppendLine(" _handle_exception_for_call(converter, token, buffer, resultRoot, exceptionRoot, argsRootBuffer);");
+ output.AppendLine(" abnormalExit = false;");
+ output.AppendLine();
+
+ if (state.MarshalString.RawReturnValue)
+ output.AppendLine(" return resultRoot.value;");
+ else if ((state.Method?.ReturnType ?? typeof(void)) == typeof(void))
+ output.AppendLine(" return;");
+ else
+ GenerateFastUnboxBlock(state);
+
+ output.AppendLine(" } finally {");
+ // An error can occur during a managed method call, in which case we will hit the finally block without having fully
+ // cleaned up from the call. In this case, allowing _teardown_after_call to throw a new exception (due to corrupt
+ // state, etc) would silence the original exception that caused the failure, so we turn it into a log message
+ output.AppendLine(" if (abnormalExit) {");
+ output.AppendLine(" try {");
+ output.AppendLine(" _teardown_after_call(converter, token, buffer, resultRoot, exceptionRoot, argsRootBuffer);");
+ output.AppendLine(" } catch (exc) {");
+ output.AppendLine(" console.error(`Unhandled error while tearing down after failed managed method call: ${exc}`);");
+ output.AppendLine(" }");
+ output.AppendLine(" } else ");
+ output.AppendLine(" _teardown_after_call(converter, token, buffer, resultRoot, exceptionRoot, argsRootBuffer);");
+ output.AppendLine(" }");
+ output.AppendLine("};");
+ output.AppendLine();
+ output.AppendLine($"return {name};");
+ }
+ }
+}
diff --git a/src/libraries/System.Private.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/DateTimeMarshaler.cs b/src/libraries/System.Private.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/DateTimeMarshaler.cs
new file mode 100644
index 00000000000000..c8e0a12090e63b
--- /dev/null
+++ b/src/libraries/System.Private.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/DateTimeMarshaler.cs
@@ -0,0 +1,36 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Diagnostics.CodeAnalysis;
+using System.Runtime.CompilerServices;
+
+namespace System.Runtime.InteropServices.JavaScript
+{
+ public static class DateTimeMarshaler
+ {
+ public static string JavaScriptToInterchangeTransform => @"
+ switch (typeof (value)) {
+ case 'number':
+ return value;
+ default:
+ if (value instanceof Date) {
+ return value.valueOf();
+ } else
+ throw new Error('Value must be a number (msecs since unix epoch), or a Date');
+ }
+";
+ public static string InterchangeToJavaScriptTransform => "return new Date(value)";
+
+ public static DateTime FromJavaScript (double msecsSinceEpoch)
+ {
+ return DateTimeOffset.FromUnixTimeMilliseconds((long)msecsSinceEpoch).UtcDateTime;
+ }
+
+ public static double ToJavaScript (in DateTime dt)
+ {
+ return (double)new DateTimeOffset(dt).ToUnixTimeMilliseconds();
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/libraries/System.Private.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/DateTimeOffsetMarshaler.cs b/src/libraries/System.Private.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/DateTimeOffsetMarshaler.cs
new file mode 100644
index 00000000000000..971de1de202d50
--- /dev/null
+++ b/src/libraries/System.Private.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/DateTimeOffsetMarshaler.cs
@@ -0,0 +1,26 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Diagnostics.CodeAnalysis;
+using System.Runtime.CompilerServices;
+
+namespace System.Runtime.InteropServices.JavaScript
+{
+ public static class DateTimeOffsetMarshaler
+ {
+ public static string JavaScriptToInterchangeTransform => DateTimeMarshaler.JavaScriptToInterchangeTransform;
+ public static string InterchangeToJavaScriptTransform => DateTimeMarshaler.InterchangeToJavaScriptTransform;
+
+ public static DateTimeOffset FromJavaScript (double msecsSinceEpoch)
+ {
+ return DateTimeOffset.FromUnixTimeMilliseconds((long)msecsSinceEpoch);
+ }
+
+ public static double ToJavaScript (in DateTimeOffset dto)
+ {
+ return (double)dto.ToUnixTimeMilliseconds();
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/libraries/System.Private.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/JSObject.References.cs b/src/libraries/System.Private.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/JSObject.References.cs
index c9b443e9c82ce1..d31f1543ce76b9 100644
--- a/src/libraries/System.Private.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/JSObject.References.cs
+++ b/src/libraries/System.Private.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/JSObject.References.cs
@@ -48,7 +48,7 @@ internal void AddInFlight()
InFlightCounter++;
if (InFlightCounter == 1)
{
- Debug.Assert(InFlight == null);
+ Debug.Assert(InFlight == null, "InFlight == null");
InFlight = GCHandle.Alloc(this, GCHandleType.Normal);
}
}
@@ -61,12 +61,12 @@ internal void ReleaseInFlight()
{
lock (this)
{
- Debug.Assert(InFlightCounter != 0);
+ Debug.Assert(InFlightCounter != 0, "InFlightCounter != 0");
InFlightCounter--;
if (InFlightCounter == 0)
{
- Debug.Assert(InFlight.HasValue);
+ Debug.Assert(InFlight.HasValue, "InFlight.HasValue");
InFlight.Value.Free();
InFlight = null;
}
diff --git a/src/libraries/System.Private.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Runtime.cs b/src/libraries/System.Private.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Runtime.cs
index 60f694e2b7cf39..fc4d081860e687 100644
--- a/src/libraries/System.Private.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Runtime.cs
+++ b/src/libraries/System.Private.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Runtime.cs
@@ -1,6 +1,9 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
+using System.Text;
+using System.Collections.Generic;
+using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Reflection;
using System.Threading.Tasks;
@@ -49,129 +52,361 @@ private struct IntPtrAndHandle
internal IntPtr ptr;
[FieldOffset(0)]
- internal RuntimeMethodHandle handle;
+ internal RuntimeMethodHandle methodHandle;
[FieldOffset(0)]
internal RuntimeTypeHandle typeHandle;
}
- // see src/mono/wasm/driver.c MARSHAL_TYPE_xxx
- public enum MarshalType : int {
- NULL = 0,
- INT = 1,
- FP64 = 2,
- STRING = 3,
- VT = 4,
- DELEGATE = 5,
- TASK = 6,
- OBJECT = 7,
- BOOL = 8,
- ENUM = 9,
- URI = 22,
- SAFEHANDLE = 23,
- ARRAY_BYTE = 10,
- ARRAY_UBYTE = 11,
- ARRAY_UBYTE_C = 12,
- ARRAY_SHORT = 13,
- ARRAY_USHORT = 14,
- ARRAY_INT = 15,
- ARRAY_UINT = 16,
- ARRAY_FLOAT = 17,
- ARRAY_DOUBLE = 18,
- FP32 = 24,
- UINT32 = 25,
- INT64 = 26,
- UINT64 = 27,
- CHAR = 28,
- STRING_INTERNED = 29,
- VOID = 30,
- ENUM64 = 31,
- POINTER = 32
+ private static RuntimeMethodHandle GetMethodHandleFromIntPtr (IntPtr ptr) {
+ var temp = new IntPtrAndHandle { ptr = ptr };
+ return temp.methodHandle;
}
- // see src/mono/wasm/driver.c MARSHAL_ERROR_xxx
- public enum MarshalError : int {
- BUFFER_TOO_SMALL = 512,
- NULL_CLASS_POINTER = 513,
- NULL_TYPE_POINTER = 514,
- UNSUPPORTED_TYPE = 515,
- FIRST = BUFFER_TOO_SMALL
+ private static RuntimeTypeHandle GetTypeHandleFromIntPtr (IntPtr ptr) {
+ var temp = new IntPtrAndHandle { ptr = ptr };
+ return temp.typeHandle;
}
- public static string GetCallSignature(IntPtr methodHandle, object objForRuntimeType)
- {
- IntPtrAndHandle tmp = default(IntPtrAndHandle);
- tmp.ptr = methodHandle;
+ private static string MakeMarshalTypeRecord (Type type, MarshalType mtype) {
+ var result = $"{{ \"marshalType\": {(int)mtype}, " +
+ $"\"typePtr\": {type.TypeHandle.Value}, " +
+ $"\"signatureChar\": \"{GetCallSignatureCharacterForMarshalType(mtype, 'a')}\" }}";
+ return result;
+ }
- MethodBase? mb = objForRuntimeType == null ? MethodBase.GetMethodFromHandle(tmp.handle) : MethodBase.GetMethodFromHandle(tmp.handle, Type.GetTypeHandle(objForRuntimeType));
- if (mb == null)
- return string.Empty;
+ private static MethodBase? MethodFromPointers (IntPtr typePtr, IntPtr methodPtr) {
+ if (methodPtr == IntPtr.Zero)
+ return null;
- ParameterInfo[] parms = mb.GetParameters();
- int parmsLength = parms.Length;
- if (parmsLength == 0)
- return string.Empty;
+ var methodHandle = GetMethodHandleFromIntPtr(methodPtr);
- char[] res = new char[parmsLength];
+ if (typePtr != IntPtr.Zero) {
+ var typeHandle = GetTypeHandleFromIntPtr(typePtr);
+ return MethodBase.GetMethodFromHandle(methodHandle, typeHandle);
+ } else {
+ return MethodBase.GetMethodFromHandle(methodHandle);
+ }
+ }
- for (int c = 0; c < parmsLength; c++)
- {
- Type t = parms[c].ParameterType;
- switch (Type.GetTypeCode(t))
- {
+ public static unsafe string? MakeMarshalSignatureInfo (IntPtr typePtr, IntPtr methodPtr) {
+ var mb = MethodFromPointers(typePtr, methodPtr);
+ if (mb is null)
+ return null;
+
+ var returnType = (mb as MethodInfo)?.ReturnType ?? typeof(void);
+ var returnMtype = GetMarshalTypeFromType(returnType);
+ var sb = new StringBuilder();
+ sb.Append("{ ");
+ sb.Append("\"result\": ");
+ sb.Append(MakeMarshalTypeRecord(returnType, returnMtype));
+ sb.Append(", \"typePtr\": ");
+ sb.Append(typePtr.ToInt32());
+ sb.Append(", \"methodPtr\": ");
+ sb.Append(methodPtr.ToInt32());
+ sb.Append(", \"parameters\": [");
+
+ int i = 0;
+ foreach (var p in mb.GetParameters()) {
+ if (i > 0)
+ sb.Append(", ");
+ sb.Append(MakeMarshalTypeRecord(p.ParameterType, GetMarshalTypeFromType(p.ParameterType)));
+ i++;
+ }
+
+ sb.Append("] }");
+
+ return sb.ToString();
+ }
+
+ [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2070:UnrecognizedReflectionPattern",
+ Justification = "Trimming doesn't affect types eligible for marshalling. Different exception for invalid inputs doesn't matter.")]
+ private static unsafe string GetAndEscapeJavascriptLiteralProperty (Type type, string name) {
+ var info = type.GetProperty(
+ name, BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic
+ );
+
+ var value = info?.GetValue(null) as string;
+ if (value is null)
+ return "null";
+
+ var sb = new StringBuilder();
+ sb.Append('\"');
+ foreach (var ch in value) {
+ switch (ch) {
+ case '\'':
+ sb.Append('\'');
+ continue;
+ case '"':
+ sb.Append('\"');
+ continue;
+ case '\\':
+ sb.Append("\\\\");
+ continue;
+ case '\n':
+ sb.Append("\\n");
+ continue;
+ }
+
+ if (ch < ' ') {
+ sb.Append("\\u");
+ sb.Append(((int)ch).ToString("X4"));
+ } else {
+ sb.Append(ch);
+ }
+ }
+ sb.Append('\"');
+
+ return sb.ToString();
+ }
+
+ [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2070:UnrecognizedReflectionPattern",
+ Justification = "Trimming doesn't affect types eligible for marshalling. Different exception for invalid inputs doesn't matter.")]
+ private static unsafe IntPtr GetMarshalMethodPointer (Type type, string name, out Type? returnType, out Type parameterType, bool hasScratchBuffer) {
+ var info = type.GetMethod(
+ name, BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic
+ );
+ if (info is null)
+ throw new WasmInteropException($"{type.Name} must have a static {name} method");
+
+ var p = info.GetParameters();
+ int expectedLength = hasScratchBuffer ? 2 : 1;
+ if ((p.Length != expectedLength) || (p[0].ParameterType is null))
+ throw new WasmInteropException($"Method {type.Name}.{name} must accept exactly {expectedLength} parameter(s)");
+
+ if (hasScratchBuffer) {
+ if ((info.ReturnType != null) && (info.ReturnType != typeof(void)))
+ throw new WasmInteropException($"Method {type.Name}.{name} must not have a return value");
+ if ((p[1].ParameterType != typeof(Span)) && (p[1].ParameterType != typeof(ReadOnlySpan)))
+ throw new WasmInteropException($"Method {type.Name}.{name}'s second parameter must be of type Span or ReadOnlySpan");
+ } else {
+ if (info.ReturnType is null)
+ throw new WasmInteropException($"Method {type.Name}.{name} must have a return value");
+ }
+
+ parameterType = p[0].ParameterType;
+ returnType = info.ReturnType;
+
+ return info.MethodHandle.Value;
+ }
+
+ [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2072:UnrecognizedReflectionPattern",
+ Justification = "Trimming doesn't affect types eligible for marshalling. Different exception for invalid inputs doesn't matter.")]
+ [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2075:UnrecognizedReflectionPattern",
+ Justification = "Trimming doesn't affect types eligible for marshalling. Different exception for invalid inputs doesn't matter.")]
+ [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2057:UnrecognizedReflectionPattern",
+ Justification = "Trimming doesn't affect types eligible for marshalling. Different exception for invalid inputs doesn't matter.")]
+ [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2026:UnrecognizedReflectionPattern",
+ Justification = "Trimming doesn't affect types eligible for marshalling. Different exception for invalid inputs doesn't matter.")]
+ public static unsafe string GetCustomMarshalerInfoForType (IntPtr typePtr, string? marshalerFullName) {
+ if ((typePtr == IntPtr.Zero) || string.IsNullOrEmpty(marshalerFullName))
+ return "null";
+
+ var typeHandle = GetTypeHandleFromIntPtr(typePtr);
+
+ var type = Type.GetTypeFromHandle(typeHandle);
+ if (type is null)
+ return "null";
+ var marshalerType = Type.GetType(marshalerFullName) ?? type.Assembly.GetType(marshalerFullName);
+ if (marshalerType is null)
+ return "null";
+
+ var scratchInfo = marshalerType.GetProperty("ScratchBufferSize", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
+ var _scratchBufferSize = scratchInfo?.GetValue(null);
+ var scratchBufferSize = _scratchBufferSize != null
+ ? (int)_scratchBufferSize
+ : (int?)null;
+
+ var jsToInterchange = GetAndEscapeJavascriptLiteralProperty(marshalerType, "JavaScriptToInterchangeTransform");
+ var interchangeToJs = GetAndEscapeJavascriptLiteralProperty(marshalerType, "InterchangeToJavaScriptTransform");
+
+ if (scratchBufferSize.HasValue) {
+ if ((jsToInterchange == "null") || (interchangeToJs == "null"))
+ throw new WasmInteropException($"{marshalerType.Name} must provide interchange transforms if it has a scratch buffer");
+ }
+
+ var inputPtr = GetMarshalMethodPointer(marshalerType, "FromJavaScript", out Type? fromReturnType, out Type fromParameterType, false);
+ var outputPtr = GetMarshalMethodPointer(marshalerType, "ToJavaScript", out Type? toReturnType, out Type toParameterType, scratchBufferSize.HasValue);
+
+ if (fromReturnType != type)
+ throw new WasmInteropException($"{marshalerType.Name}.FromJavaScript's return type must be {type.Name} but was {fromReturnType}");
+
+ if (type.IsValueType) {
+ var typeMatches = toParameterType.GetElementType() == type;
+ if (!typeMatches || !(toParameterType.IsPointer || toParameterType.IsByRef))
+ throw new WasmInteropException($"{marshalerType.Name}.ToJavaScript's parameter must be 'in {type.Name}' or '{type.Name}*' but was {toParameterType}");
+ } else {
+ if (toParameterType != type)
+ throw new WasmInteropException($"{marshalerType.Name}.ToJavaScript's parameter must be of type {type.Name} but was {toParameterType}");
+ }
+
+ var result = new StringBuilder();
+ result.AppendLine("{");
+ result.AppendLine($"\"typePtr\": {typePtr},");
+ if (scratchBufferSize.HasValue)
+ result.AppendLine($"\"scratchBufferSize\": {scratchBufferSize.Value},");
+ result.AppendLine($"\"jsToInterchange\": {jsToInterchange},");
+ result.AppendLine($"\"interchangeToJs\": {interchangeToJs},");
+ result.AppendLine($"\"inputPtr\": {inputPtr},");
+ result.AppendLine($"\"outputPtr\": {outputPtr}");
+ result.AppendLine("}");
+ return result.ToString();
+ }
+
+ internal static MarshalType GetMarshalTypeFromType (Type? type) {
+ if (type is null)
+ return MarshalType.VOID;
+
+ var typeCode = Type.GetTypeCode(type);
+ if (type.IsEnum) {
+ switch (typeCode) {
+ case TypeCode.Int32:
+ case TypeCode.UInt32:
+ return MarshalType.ENUM;
+ case TypeCode.Int64:
+ case TypeCode.UInt64:
+ return MarshalType.ENUM64;
+ default:
+ throw new WasmInteropException($"Unsupported enum underlying type {typeCode}");
+ }
+ }
+
+ switch (typeCode) {
+ case TypeCode.Byte:
+ case TypeCode.SByte:
+ case TypeCode.Int16:
+ case TypeCode.UInt16:
+ case TypeCode.Int32:
+ return MarshalType.INT;
+ case TypeCode.UInt32:
+ return MarshalType.UINT32;
+ case TypeCode.Boolean:
+ return MarshalType.BOOL;
+ case TypeCode.Int64:
+ return MarshalType.INT64;
+ case TypeCode.UInt64:
+ return MarshalType.UINT64;
+ case TypeCode.Single:
+ return MarshalType.FP32;
+ case TypeCode.Double:
+ return MarshalType.FP64;
+ case TypeCode.String:
+ return MarshalType.STRING;
+ case TypeCode.Char:
+ return MarshalType.CHAR;
+ }
+
+ if (type.IsArray) {
+ if (!type.IsSZArray)
+ throw new WasmInteropException("Only single-dimensional arrays with a zero lower bound can be marshaled to JS");
+
+ var elementType = type.GetElementType();
+ switch (Type.GetTypeCode(elementType)) {
case TypeCode.Byte:
+ return MarshalType.ARRAY_UBYTE;
case TypeCode.SByte:
+ return MarshalType.ARRAY_BYTE;
case TypeCode.Int16:
+ return MarshalType.ARRAY_SHORT;
case TypeCode.UInt16:
+ return MarshalType.ARRAY_USHORT;
case TypeCode.Int32:
+ return MarshalType.ARRAY_INT;
case TypeCode.UInt32:
- case TypeCode.Boolean:
- // Enums types have the same code as their underlying numeric types
- if (t.IsEnum)
- res[c] = 'j';
- else
- res[c] = 'i';
- break;
- case TypeCode.Int64:
- case TypeCode.UInt64:
- // Enums types have the same code as their underlying numeric types
- if (t.IsEnum)
- res[c] = 'k';
- else
- res[c] = 'l';
- break;
+ return MarshalType.ARRAY_UINT;
case TypeCode.Single:
- res[c] = 'f';
- break;
+ return MarshalType.ARRAY_FLOAT;
case TypeCode.Double:
- res[c] = 'd';
- break;
- case TypeCode.String:
- res[c] = 's';
- break;
+ return MarshalType.ARRAY_DOUBLE;
default:
- if (t == typeof(IntPtr))
- {
- res[c] = 'i';
- }
- else if (t == typeof(Uri))
- {
- res[c] = 'u';
- }
- else if (t == typeof(SafeHandle))
- {
- res[c] = 'h';
- }
- else
- {
- if (t.IsValueType)
- throw new NotSupportedException(SR.ValueTypeNotSupported);
- res[c] = 'o';
- }
- break;
+ throw new WasmInteropException($"Unsupported array element type {elementType}");
}
+ } else if (type == typeof(IntPtr))
+ return MarshalType.POINTER;
+ else if (type == typeof(UIntPtr))
+ return MarshalType.POINTER;
+ else if (type == typeof(SafeHandle))
+ return MarshalType.SAFEHANDLE;
+ else if (typeof(Delegate).IsAssignableFrom(type))
+ return MarshalType.DELEGATE;
+ else if ((type == typeof(Task)) || typeof(Task).IsAssignableFrom(type))
+ return MarshalType.TASK;
+ // HACK: You could theoretically inherit from Uri, but I consider this out of scope.
+ // If you really need to marshal a custom Uri, define a custom marshaler for it
+ else if (typeof(Uri) == type)
+ return MarshalType.URI;
+ else if ((type == typeof(Span)) || (type == typeof(ReadOnlySpan)))
+ return MarshalType.SPAN_BYTE;
+ else if (type.IsPointer)
+ return MarshalType.POINTER;
+
+ if (type.IsValueType)
+ return MarshalType.VT;
+ else
+ return MarshalType.OBJECT;
+ }
+
+ internal static char GetCallSignatureCharacterForMarshalType (MarshalType t, char? defaultValue) {
+ switch (t) {
+ case MarshalType.BOOL:
+ case MarshalType.INT:
+ case MarshalType.UINT32:
+ case MarshalType.POINTER:
+ return 'i';
+ case MarshalType.UINT64:
+ case MarshalType.INT64:
+ return 'l';
+ case MarshalType.FP32:
+ return 'f';
+ case MarshalType.FP64:
+ return 'd';
+ case MarshalType.STRING:
+ return 's';
+ case MarshalType.URI:
+ return 'u';
+ case MarshalType.SAFEHANDLE:
+ return 'h';
+ case MarshalType.ENUM:
+ return 'j';
+ case MarshalType.ENUM64:
+ return 'k';
+ case MarshalType.TASK:
+ case MarshalType.DELEGATE:
+ case MarshalType.OBJECT:
+ return 'o';
+ case MarshalType.VT:
+ return 'a';
+ case MarshalType.SPAN_BYTE:
+ return 'b';
+ default:
+ if (defaultValue.HasValue)
+ return defaultValue.Value;
+ else
+ throw new WasmInteropException($"Unsupported marshal type {t}");
+ }
+ }
+
+ public static string GetCallSignature(IntPtr _methodHandle, object? objForRuntimeType)
+ {
+ var methodHandle = GetMethodHandleFromIntPtr(_methodHandle);
+
+ MethodBase? mb = objForRuntimeType is null ? MethodBase.GetMethodFromHandle(methodHandle) : MethodBase.GetMethodFromHandle(methodHandle, Type.GetTypeHandle(objForRuntimeType));
+ if (mb is null)
+ return string.Empty;
+
+ ParameterInfo[] parms = mb.GetParameters();
+ int parmsLength = parms.Length;
+ if (parmsLength == 0)
+ return string.Empty;
+
+ var result = new char[parmsLength];
+ for (int i = 0; i < parmsLength; i++) {
+ Type t = parms[i].ParameterType;
+ var mt = GetMarshalTypeFromType(t);
+ result[i] = GetCallSignatureCharacterForMarshalType(mt, null);
}
- return new string(res);
+
+ return new string(result);
}
///
@@ -198,33 +433,9 @@ public static string GetCallSignature(IntPtr methodHandle, object objForRuntimeT
return null;
}
- public static string ObjectToString(object o)
+ public static string ObjectToString(object? o)
{
- return o.ToString() ?? string.Empty;
- }
-
- public static double GetDateValue(object dtv)
- {
- if (dtv == null)
- throw new ArgumentNullException(nameof(dtv));
- if (!(dtv is DateTime dt))
- throw new InvalidCastException(SR.Format(SR.UnableCastObjectToType, dtv.GetType(), typeof(DateTime)));
- if (dt.Kind == DateTimeKind.Local)
- dt = dt.ToUniversalTime();
- else if (dt.Kind == DateTimeKind.Unspecified)
- dt = new DateTime(dt.Ticks, DateTimeKind.Utc);
- return new DateTimeOffset(dt).ToUnixTimeMilliseconds();
- }
-
- public static DateTime CreateDateTime(double ticks)
- {
- DateTimeOffset unixTime = DateTimeOffset.FromUnixTimeMilliseconds((long)ticks);
- return unixTime.DateTime;
- }
-
- public static Uri CreateUri(string uri)
- {
- return new Uri(uri);
+ return o?.ToString() ?? string.Empty;
}
public static void CancelPromise(int promiseJSHandle)
@@ -296,5 +507,36 @@ public static void WebSocketAbort(JSObject webSocket)
if (exception != 0)
throw new JSException(res);
}
+
+ public static string GenerateArgsMarshaler (IntPtr typeHandle, IntPtr methodHandle, string signature) {
+ MethodBase? method;
+ try {
+ // It's generally harmless for this to fail unless the signature contains an 'a', so we log it and continue
+ method = MethodFromPointers(typeHandle, methodHandle);
+ } catch (Exception exc) {
+ Debug.WriteLine($"Failed to resolve method when generating marshaler: {exc.Message}");
+ method = null;
+ }
+
+ var state = new Codegen.MarshalBuilderState {
+ MarshalString = new MarshalString(signature, method)
+ };
+ Codegen.GenerateSignatureConverter(state);
+ return state.Output.ToString();
+ }
+
+ public static string GenerateBoundMethod (IntPtr typeHandle, IntPtr methodHandle, string signature, string? friendlyName) {
+ MethodBase? method;
+ method = MethodFromPointers(typeHandle, methodHandle);
+ if (method == null)
+ throw new Exception("Failed to resolve method");
+
+ var state = new Codegen.BoundMethodBuilderState((MethodInfo)method) {
+ MarshalString = new MarshalString(signature, method),
+ FriendlyName = friendlyName,
+ };
+ Codegen.GenerateBoundMethod(state);
+ return state.Output.ToString();
+ }
}
}
diff --git a/src/libraries/System.Private.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Types.cs b/src/libraries/System.Private.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Types.cs
new file mode 100644
index 00000000000000..3db975f78c4337
--- /dev/null
+++ b/src/libraries/System.Private.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Types.cs
@@ -0,0 +1,114 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Text;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Diagnostics.CodeAnalysis;
+using System.Reflection;
+using System.Threading.Tasks;
+
+namespace System.Runtime.InteropServices.JavaScript
+{
+ // see src/mono/wasm/driver.c MARSHAL_TYPE_xxx
+ public enum MarshalType : int {
+ NULL = 0,
+ INT = 1,
+ FP64 = 2,
+ STRING = 3,
+ VT = 4,
+ DELEGATE = 5,
+ TASK = 6,
+ OBJECT = 7,
+ BOOL = 8,
+ ENUM = 9,
+ URI = 22,
+ SAFEHANDLE = 23,
+ ARRAY_BYTE = 10,
+ ARRAY_UBYTE = 11,
+ ARRAY_UBYTE_C = 12,
+ ARRAY_SHORT = 13,
+ ARRAY_USHORT = 14,
+ ARRAY_INT = 15,
+ ARRAY_UINT = 16,
+ ARRAY_FLOAT = 17,
+ ARRAY_DOUBLE = 18,
+ FP32 = 24,
+ UINT32 = 25,
+ INT64 = 26,
+ UINT64 = 27,
+ CHAR = 28,
+ STRING_INTERNED = 29,
+ VOID = 30,
+ ENUM64 = 31,
+ POINTER = 32,
+ SPAN_BYTE = 33,
+ }
+
+ // see src/mono/wasm/driver.c MARSHAL_ERROR_xxx
+ public enum MarshalError : int {
+ BUFFER_TOO_SMALL = 512,
+ NULL_CLASS_POINTER = 513,
+ NULL_TYPE_POINTER = 514,
+ UNSUPPORTED_TYPE = 515,
+ FIRST = BUFFER_TOO_SMALL
+ }
+
+ public enum ArgsMarshalCharacter {
+ Int32 = 'i', // int32
+ Int32Enum = 'j', // int32 - Enum with underlying type of int32
+ Int64 = 'l', // int64
+ Int64Enum = 'k', // int64 - Enum with underlying type of int64
+ Float32 = 'f', // float
+ Float64 = 'd', // double
+ String = 's', // string
+ InternedString = 'S', // interned string
+ Uri = 'u',
+ JSObj = 'o', // js object will be converted to a C# object (this will box numbers/bool/promises)
+ MONOObj = 'm', // raw mono object. Don't use it unless you know what you're doing
+ Auto = 'a', // the bindings layer will select an appropriate converter based on the C# method signature
+ ByteSpan = 'b', // Span
+ }
+
+ public struct MarshalString {
+ public string Signature { get; private set; }
+ public string Key { get; private set; }
+ public MethodBase? Method { get; private set; }
+ public int ArgumentCount { get; private set; }
+ public bool RawReturnValue { get; private set; }
+ public bool ContainsAuto { get; private set; }
+
+ public MarshalString (string s, MethodBase? method = null) {
+ Signature = s;
+ Method = method;
+ RawReturnValue = s.EndsWith("!");
+ ArgumentCount = Signature.Length;
+ ContainsAuto = s.Contains((char)(int)ArgsMarshalCharacter.Auto);
+
+ if (RawReturnValue)
+ ArgumentCount -= 1;
+
+ var keySig = Signature.Replace("!", "_result_unmarshaled");
+ if (keySig.Length == 0)
+ keySig = "$void";
+
+ if (ContainsAuto && (Method != null))
+ Key = $"{keySig}_m{Method.MethodHandle.Value.ToInt32()}";
+ else
+ Key = keySig;
+ }
+
+ public ArgsMarshalCharacter this [int index] =>
+ (ArgsMarshalCharacter)(int)Signature[index];
+ }
+
+ public class WasmInteropException : Exception {
+ public WasmInteropException (string message)
+ : base (message) {
+ }
+
+ public WasmInteropException (string message, Exception innerException)
+ : base (message, innerException) {
+ }
+ }
+}
diff --git a/src/libraries/System.Private.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/UriMarshaler.cs b/src/libraries/System.Private.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/UriMarshaler.cs
new file mode 100644
index 00000000000000..8f319e4f4ade65
--- /dev/null
+++ b/src/libraries/System.Private.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/UriMarshaler.cs
@@ -0,0 +1,26 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Diagnostics.CodeAnalysis;
+using System;
+using System.Runtime.CompilerServices;
+
+namespace System.Runtime.InteropServices.JavaScript
+{
+ public static class UriMarshaler
+ {
+ public static Uri FromJavaScript (string s)
+ {
+ return new Uri(s);
+ }
+
+ public static string ToJavaScript (Uri u)
+ {
+ // FIXME: Uri.ToString() escapes certain characters in URIs.
+ // This may not be desirable, but the old marshaler seems to have had this limitation too.
+ return u.ToString();
+ }
+ }
+}
diff --git a/src/libraries/System.Private.Runtime.InteropServices.JavaScript/tests/ILLink.Descriptors.xml b/src/libraries/System.Private.Runtime.InteropServices.JavaScript/tests/ILLink.Descriptors.xml
new file mode 100644
index 00000000000000..a6b235ad8f42db
--- /dev/null
+++ b/src/libraries/System.Private.Runtime.InteropServices.JavaScript/tests/ILLink.Descriptors.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
diff --git a/src/libraries/System.Private.Runtime.InteropServices.JavaScript/tests/System.Private.Runtime.InteropServices.JavaScript.Tests.csproj b/src/libraries/System.Private.Runtime.InteropServices.JavaScript/tests/System.Private.Runtime.InteropServices.JavaScript.Tests.csproj
index f1afdecab341e2..af19b9664ca542 100644
--- a/src/libraries/System.Private.Runtime.InteropServices.JavaScript/tests/System.Private.Runtime.InteropServices.JavaScript.Tests.csproj
+++ b/src/libraries/System.Private.Runtime.InteropServices.JavaScript/tests/System.Private.Runtime.InteropServices.JavaScript.Tests.csproj
@@ -4,7 +4,19 @@
$(NetCoreAppCurrent)-Browser
true
$(WasmXHarnessArgs) --engine-arg=--expose-gc --web-server-use-cop
+ ILLink.Descriptors.xml
+ ILLink.Descriptors.xml
+
+
+
+
+
+
diff --git a/src/libraries/System.Private.Runtime.InteropServices.JavaScript/tests/System/Runtime/InteropServices/JavaScript/HelperMarshal.cs b/src/libraries/System.Private.Runtime.InteropServices.JavaScript/tests/System/Runtime/InteropServices/JavaScript/HelperMarshal.cs
index f612dcb76be527..95c0290e9c6f62 100644
--- a/src/libraries/System.Private.Runtime.InteropServices.JavaScript/tests/System/Runtime/InteropServices/JavaScript/HelperMarshal.cs
+++ b/src/libraries/System.Private.Runtime.InteropServices.JavaScript/tests/System/Runtime/InteropServices/JavaScript/HelperMarshal.cs
@@ -1,17 +1,127 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
+using System.Runtime.InteropServices;
using System.Runtime.InteropServices.JavaScript;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
+using System.Runtime.CompilerServices;
using Xunit;
namespace System.Runtime.InteropServices.JavaScript.Tests
{
public static class HelperMarshal
{
+ public static class CustomClassMarshaler {
+ public static CustomClass FromJavaScript (double d) {
+ return new CustomClass { D = d };
+ }
+
+ public static double ToJavaScript (CustomClass ct) {
+ return ct?.D ?? -1;
+ }
+ }
+
+ public class CustomClass {
+ public double D;
+ }
+
+ public static class CustomStructMarshaler {
+ public static CustomStruct FromJavaScript (double d) {
+ return new CustomStruct { D = d };
+ }
+
+ public static double ToJavaScript (in CustomStruct ct) {
+ return ct.D;
+ }
+ }
+
+ public struct CustomStruct {
+ public double D;
+ }
+
+ public static class CustomDateMarshaler {
+ public static string JavaScriptToInterchangeTransform => "return value.toISOString()";
+ public static string InterchangeToJavaScriptTransform => "return new Date(value)";
+
+ public static CustomDate FromJavaScript (string s) {
+ var newDate = DateTime.Parse(s).ToUniversalTime();
+ return new CustomDate {
+ Date = newDate
+ };
+ }
+
+ public static string ToJavaScript (in CustomDate cd) {
+ var result = cd.Date.ToString("o");
+ return result;
+ }
+ }
+
+ public struct CustomDate {
+ public DateTime Date;
+ }
+
+ [StructLayout(LayoutKind.Sequential)]
+ public struct CustomVector3 {
+ public float X, Y, Z;
+
+ public override string ToString () {
+ return $"[{X}, {Y}, {Z}]";
+ }
+ }
+
+ public static unsafe class CustomVector3Marshaler {
+ public static int ScratchBufferSize => sizeof(CustomVector3);
+ public static string JavaScriptToInterchangeTransform =>
+ @"
+ if (bufferSize !== 12)
+ throw new Error('Invalid buffer size');
+ if (!Array.isArray(value) || (value.length !== 3))
+ throw new Error('Invalid vector3, expected [f, f, f]');
+ setF32(buffer + 0, value[0]);
+ setF32(buffer + 4, value[1]);
+ setF32(buffer + 8, value[2]);
+ ";
+
+ public static string InterchangeToJavaScriptTransform =>
+ @"
+ if (bufferSize !== 12)
+ throw new Error('Invalid buffer size');
+ return [getF32(buffer + 0), getF32(buffer + 4), getF32(buffer + 8)];
+ ";
+
+ public static void ToJavaScript (ref CustomVector3 value, Span buffer) {
+ MemoryMarshal.Write(buffer, ref value);
+ }
+
+ public static CustomVector3 FromJavaScript (ReadOnlySpan buffer) {
+ return MemoryMarshal.AsRef(buffer);
+ }
+ }
+
internal const string INTEROP_CLASS = "[System.Private.Runtime.InteropServices.JavaScript.Tests]System.Runtime.InteropServices.JavaScript.Tests.HelperMarshal:";
+
+ internal static CustomClass _ccValue;
+ private static void InvokeCustomClass(CustomClass cc)
+ {
+ _ccValue = cc;
+ }
+ private static CustomClass ReturnCustomClass(CustomClass cc)
+ {
+ return cc;
+ }
+
+ internal static CustomStruct _csValue;
+ private unsafe static void InvokeCustomStruct(CustomStruct cs)
+ {
+ _csValue = cs;
+ }
+ private static CustomStruct ReturnCustomStruct(CustomStruct cs)
+ {
+ return cs;
+ }
+
internal static int _i32Value;
private static void InvokeI32(int a, int b)
{
@@ -104,6 +214,61 @@ private static object InvokeObj2(object obj)
return obj;
}
+ internal static DateTime _dateTimeValue;
+ private static void InvokeDateTime(object boxed)
+ {
+ _dateTimeValue = (DateTime)boxed;
+ }
+ private static void InvokeDateTimeOffset(DateTimeOffset dto)
+ {
+ // FIXME
+ _dateTimeValue = dto.DateTime;
+ }
+ private static void InvokeDateTimeByValue(DateTime dt)
+ {
+ _dateTimeValue = dt;
+ }
+ private static void InvokeCustomDate(CustomDate cd)
+ {
+ _dateTimeValue = cd.Date;
+ }
+ private static CustomDate ReturnCustomDate(CustomDate cd)
+ {
+ return cd;
+ }
+
+ internal static CustomVector3 _vec3Value;
+ private static void InvokeCustomVector3(CustomVector3 cv3)
+ {
+ _vec3Value = cv3;
+ }
+ private static CustomVector3 MakeCustomVector3(float x, float y, float z)
+ {
+ return new CustomVector3 {
+ X = x,
+ Y = y,
+ Z = z
+ };
+ }
+ private static CustomVector3 ReturnCustomVector3(CustomVector3 cv3)
+ {
+ return cv3;
+ }
+ private static CustomVector3 AddCustomVector3(CustomVector3 lhs, CustomVector3 rhs)
+ {
+ return new CustomVector3 {
+ X = lhs.X + rhs.X,
+ Y = lhs.Y + rhs.Y,
+ Z = lhs.Z + rhs.Z
+ };
+ }
+
+ internal static System.Uri _uriValue;
+ private static void InvokeUri(System.Uri uri)
+ {
+ _uriValue = uri;
+ }
+
internal static object _marshalledObject;
private static object InvokeMarshalObj()
{
@@ -642,65 +807,65 @@ private static Func> CreateFunctionAcceptingArray()
};
}
- public static Task SynchronousTask()
+ public static Task SynchronousTask()
{
return Task.CompletedTask;
}
- public static async Task AsynchronousTask()
+ public static async Task AsynchronousTask()
{
await Task.Yield();
}
- public static Task SynchronousTaskInt(int i)
+ public static Task SynchronousTaskInt(int i)
{
return Task.FromResult(i);
}
- public static async Task AsynchronousTaskInt(int i)
+ public static async Task AsynchronousTaskInt(int i)
{
await Task.Yield();
return i;
}
- public static Task FailedSynchronousTask()
+ public static Task FailedSynchronousTask()
{
return Task.FromException(new Exception());
}
- public static async Task FailedAsynchronousTask()
+ public static async Task FailedAsynchronousTask()
{
await Task.Yield();
throw new Exception();
}
- public static async ValueTask AsynchronousValueTask()
+ public static async ValueTask AsynchronousValueTask()
{
await Task.Yield();
}
- public static ValueTask SynchronousValueTask()
+ public static ValueTask SynchronousValueTask()
{
return ValueTask.CompletedTask;
}
- public static ValueTask SynchronousValueTaskInt(int i)
+ public static ValueTask SynchronousValueTaskInt(int i)
{
return ValueTask.FromResult(i);
}
- public static async ValueTask AsynchronousValueTaskInt(int i)
+ public static async ValueTask AsynchronousValueTaskInt(int i)
{
await Task.Yield();
return i;
}
- public static ValueTask FailedSynchronousValueTask()
+ public static ValueTask FailedSynchronousValueTask()
{
return ValueTask.FromException(new Exception());
}
- public static async ValueTask FailedAsynchronousValueTask()
+ public static async ValueTask FailedAsynchronousValueTask()
{
await Task.Yield();
throw new Exception();
diff --git a/src/libraries/System.Private.Runtime.InteropServices.JavaScript/tests/System/Runtime/InteropServices/JavaScript/JavaScriptTests.cs b/src/libraries/System.Private.Runtime.InteropServices.JavaScript/tests/System/Runtime/InteropServices/JavaScript/JavaScriptTests.cs
index 7158c38899c13a..d97a0b807b83b5 100644
--- a/src/libraries/System.Private.Runtime.InteropServices.JavaScript/tests/System/Runtime/InteropServices/JavaScript/JavaScriptTests.cs
+++ b/src/libraries/System.Private.Runtime.InteropServices.JavaScript/tests/System/Runtime/InteropServices/JavaScript/JavaScriptTests.cs
@@ -464,7 +464,9 @@ public static void RoundtripCSDate()
var obj = (JSObject)factory.Call(null, date);
var dummy = (DateTime)obj.GetObjectProperty("dummy");
- Assert.Equal(date, dummy);
+ // HACK: JS Dates do not contain timezone information, so date marshaling converts all dates to
+ // UTC dates.
+ Assert.Equal(date.ToUniversalTime(), dummy.ToUniversalTime());
}
[Fact]
diff --git a/src/libraries/System.Private.Runtime.InteropServices.JavaScript/tests/System/Runtime/InteropServices/JavaScript/MarshalTests.cs b/src/libraries/System.Private.Runtime.InteropServices.JavaScript/tests/System/Runtime/InteropServices/JavaScript/MarshalTests.cs
index f8955384981e0e..d40d955fac4924 100644
--- a/src/libraries/System.Private.Runtime.InteropServices.JavaScript/tests/System/Runtime/InteropServices/JavaScript/MarshalTests.cs
+++ b/src/libraries/System.Private.Runtime.InteropServices.JavaScript/tests/System/Runtime/InteropServices/JavaScript/MarshalTests.cs
@@ -69,7 +69,7 @@ public static void MarshalArrayBuffer2Int2()
for (var i = 0; i < int32View.length; i++) {
int32View[i] = i * 2;
}
- App.call_test_method (""MarshalByteBufferToInts"", [ buffer ]);
+ App.call_test_method (""MarshalByteBufferToInts"", [ buffer ]);
");
Assert.Equal(4, HelperMarshal._intBuffer.Length);
@@ -344,7 +344,7 @@ public static void GetObjectProperties()
{
Runtime.InvokeJS(@"
var obj = {myInt: 100, myDouble: 4.5, myString: ""Hic Sunt Dracones"", myBoolean: true};
- App.call_test_method (""RetrieveObjectProperties"", [ obj ]);
+ App.call_test_method (""RetrieveObjectProperties"", [ obj ]);
");
Assert.Equal(100, HelperMarshal._jsProperties[0]);
@@ -358,8 +358,8 @@ public static void SetObjectProperties()
{
Runtime.InvokeJS(@"
var obj = {myInt: 200, myDouble: 0, myString: ""foo"", myBoolean: false};
- App.call_test_method (""PopulateObjectProperties"", [ obj, false ]);
- App.call_test_method (""RetrieveObjectProperties"", [ obj ]);
+ App.call_test_method (""PopulateObjectProperties"", [ obj, false ]);
+ App.call_test_method (""RetrieveObjectProperties"", [ obj ]);
");
Assert.Equal(100, HelperMarshal._jsProperties[0]);
@@ -374,8 +374,8 @@ public static void SetObjectPropertiesIfNotExistsFalse()
// This test will not create the properties if they do not already exist
Runtime.InvokeJS(@"
var obj = {myInt: 200};
- App.call_test_method (""PopulateObjectProperties"", [ obj, false ]);
- App.call_test_method (""RetrieveObjectProperties"", [ obj ]);
+ App.call_test_method (""PopulateObjectProperties"", [ obj, false ]);
+ App.call_test_method (""RetrieveObjectProperties"", [ obj ]);
");
Assert.Equal(100, HelperMarshal._jsProperties[0]);
@@ -387,7 +387,7 @@ public static void SetObjectPropertiesIfNotExistsFalse()
[Fact]
public static void SetObjectPropertiesIfNotExistsTrue()
{
- // This test will set the value of the property if it exists and will create and
+ // This test will set the value of the property if it exists and will create and
// set the value if it does not exists
Runtime.InvokeJS(@"
var obj = {myInt: 200};
@@ -407,7 +407,7 @@ public static void MarshalTypedArray()
Runtime.InvokeJS(@"
var buffer = new ArrayBuffer(16);
var uint8View = new Uint8Array(buffer);
- App.call_test_method (""MarshalByteBuffer"", [ uint8View ]);
+ App.call_test_method (""MarshalByteBuffer"", [ uint8View ]);
");
Assert.Equal(16, HelperMarshal._byteBuffer.Length);
@@ -437,7 +437,7 @@ public static void MarshalTypedArray2Float()
{
Runtime.InvokeJS(@"
var typedArray = new Float32Array([1, 2.1334, 3, 4.2, 5]);
- App.call_test_method (""MarshalFloat32Array"", [ typedArray ]);
+ App.call_test_method (""MarshalFloat32Array"", [ typedArray ]);
");
Assert.Equal(1, HelperMarshal._floatBuffer[0]);
@@ -456,7 +456,7 @@ public static void MarshalArrayBuffer2Float2()
for (var i = 0; i < float32View.length; i++) {
float32View[i] = i * 2.5;
}
- App.call_test_method (""MarshalArrayBufferToFloat32Array"", [ buffer ]);
+ App.call_test_method (""MarshalArrayBufferToFloat32Array"", [ buffer ]);
");
Assert.Equal(4, HelperMarshal._floatBuffer.Length);
@@ -471,7 +471,7 @@ public static void MarshalTypedArray2Double()
{
Runtime.InvokeJS(@"
var typedArray = new Float64Array([1, 2.1334, 3, 4.2, 5]);
- App.call_test_method (""MarshalFloat64Array"", [ typedArray ]);
+ App.call_test_method (""MarshalFloat64Array"", [ typedArray ]);
");
Assert.Equal(1, HelperMarshal._doubleBuffer[0]);
@@ -490,7 +490,7 @@ public static void MarshalArrayBuffer2Double()
for (var i = 0; i < float64View.length; i++) {
float64View[i] = i * 2.5;
}
- App.call_test_method (""MarshalByteBufferToDoubles"", [ buffer ]);
+ App.call_test_method (""MarshalByteBufferToDoubles"", [ buffer ]);
");
Assert.Equal(4, HelperMarshal._doubleBuffer.Length);
@@ -509,7 +509,7 @@ public static void MarshalArrayBuffer2Double2()
for (var i = 0; i < float64View.length; i++) {
float64View[i] = i * 2.5;
}
- App.call_test_method (""MarshalArrayBufferToFloat64Array"", [ buffer ]);
+ App.call_test_method (""MarshalArrayBufferToFloat64Array"", [ buffer ]);
");
Assert.Equal(4, HelperMarshal._doubleBuffer.Length);
@@ -621,7 +621,7 @@ public static void TestFunctionApply()
");
Assert.Equal(2, HelperMarshal._minValue);
}
-
+
[Fact]
public static void BoundStaticMethodMissingArgs()
{
@@ -636,7 +636,7 @@ public static void BoundStaticMethodMissingArgs()
");
Assert.Equal(0, HelperMarshal._intValue);
}
-
+
[Fact]
public static void BoundStaticMethodExtraArgs()
{
@@ -647,7 +647,7 @@ public static void BoundStaticMethodExtraArgs()
");
Assert.Equal(200, HelperMarshal._intValue);
}
-
+
[Fact]
public static void BoundStaticMethodArgumentTypeCoercion()
{
@@ -667,7 +667,7 @@ public static void BoundStaticMethodArgumentTypeCoercion()
");
Assert.Equal(400, HelperMarshal._intValue);
}
-
+
[Fact]
public static void BoundStaticMethodUnpleasantArgumentTypeCoercion()
{
@@ -697,7 +697,7 @@ public static void PassUintArgument()
Assert.Equal(0xFFFFFFFEu, HelperMarshal._uintValue);
}
-
+
[Fact]
public static void ReturnUintEnum ()
{
@@ -711,7 +711,7 @@ public static void ReturnUintEnum ()
");
Assert.Equal((uint)TestEnum.BigValue, HelperMarshal._uintValue);
}
-
+
[Fact]
public static void PassUintEnumByValue ()
{
@@ -722,7 +722,7 @@ public static void PassUintEnumByValue ()
");
Assert.Equal(TestEnum.BigValue, HelperMarshal._enumValue);
}
-
+
[Fact]
public static void PassUintEnumByValueMasqueradingAsInt ()
{
@@ -735,12 +735,12 @@ public static void PassUintEnumByValueMasqueradingAsInt ()
");
Assert.Equal(TestEnum.BigValue, HelperMarshal._enumValue);
}
-
+
[Fact]
public static void PassUintEnumByNameIsNotImplemented ()
{
HelperMarshal._enumValue = TestEnum.Zero;
- var exc = Assert.Throws( () =>
+ var exc = Assert.Throws( () =>
Runtime.InvokeJS(@$"
var set_enum = INTERNAL.mono_bind_static_method (""{HelperMarshal.INTEROP_CLASS}SetEnumValue"", ""j"");
set_enum (""BigValue"");
@@ -748,11 +748,11 @@ public static void PassUintEnumByNameIsNotImplemented ()
);
Assert.StartsWith("Error: Expected numeric value for enum argument, got 'BigValue'", exc.Message);
}
-
+
[Fact]
public static void CannotUnboxUint64 ()
{
- var exc = Assert.Throws( () =>
+ var exc = Assert.Throws( () =>
Runtime.InvokeJS(@$"
var get_u64 = INTERNAL.mono_bind_static_method (""{HelperMarshal.INTEROP_CLASS}GetUInt64"", """");
var u64 = get_u64();
@@ -860,6 +860,9 @@ public static void SymbolsAreMarshaledAsStrings()
Assert.True(Object.ReferenceEquals(HelperMarshal._stringResource, HelperMarshal._stringResource2));
}
+ const string ExpectedDateString = "1937-07-02T05:35:02.0000000Z";
+ static readonly DateTime ExpectedDateTime = DateTime.Parse(ExpectedDateString).ToUniversalTime();
+
[Fact]
public static void InternedStringReturnValuesWork()
{
@@ -902,16 +905,16 @@ public static void InvokeJSNotInGlobalScope()
Assert.Null(result);
}
- private static async Task MarshalTask(string helperMethodName, string helperMethodArgs = "", string resolvedBody = "")
+ private static async Task MarshalTask(string helperMethodName, string helperMethodArgs = "", string resolvedBody = "")
{
Runtime.InvokeJS(
@"globalThis.__test_promise_completed = false; " +
@"globalThis.__test_promise_resolved = false; " +
@"globalThis.__test_promise_failed = false; " +
$@"var t = App.call_test_method ('{helperMethodName}', [ {helperMethodArgs} ], 'i'); " +
- "t.then(result => { globalThis.__test_promise_resolved = true; " + resolvedBody + " })" +
+ "t.then(result => { globalThis.__test_promise_resolved = true; " + resolvedBody + " })" +
" .catch(e => { globalThis.__test_promise_failed = true; })" +
- " .finally(result => { globalThis.__test_promise_completed = true; }); " +
+ " .finally(result => { globalThis.__test_promise_completed = true; }); " +
""
);
@@ -1019,5 +1022,181 @@ public static async Task MarshalFailedAsynchronousValueTaskDoesNotWorkYet()
bool success = await MarshalTask("FailedAsynchronousValueTask");
Assert.False(success, "FailedAsynchronousValueTask didn't failed.");
}
+
+ [Fact]
+ public static void MarshalDateTime()
+ {
+ HelperMarshal._dateTimeValue = default(DateTime);
+ Runtime.InvokeJS(
+ $"var dt = new Date('{ExpectedDateString}');\r\n" +
+ "App.call_test_method ('InvokeDateTime', [ dt ], 'o');"
+ );
+ Assert.Equal(ExpectedDateTime, HelperMarshal._dateTimeValue);
+ }
+
+ [Fact]
+ public static void MarshalDateTimeDefault()
+ {
+ HelperMarshal._dateTimeValue = default(DateTime);
+ Runtime.InvokeJS(
+ $"var dt = new Date('{ExpectedDateString}');\r\n" +
+ "App.call_test_method ('InvokeDateTime', [ dt ]);"
+ );
+ Assert.Equal(ExpectedDateTime, HelperMarshal._dateTimeValue);
+ }
+
+ [Fact]
+ public static void MarshalDateTimeAutomatic()
+ {
+ HelperMarshal._dateTimeValue = default(DateTime);
+ Runtime.InvokeJS(
+ $"var dt = new Date('{ExpectedDateString}');\r\n" +
+ "App.call_test_method ('InvokeDateTime', [ dt ], 'a');"
+ );
+ Assert.Equal(ExpectedDateTime, HelperMarshal._dateTimeValue);
+ }
+
+ [Fact]
+ public static void MarshalDateTimeOffsetAutomatic()
+ {
+ HelperMarshal._dateTimeValue = default(DateTime);
+ Runtime.InvokeJS(
+ $"var dt = new Date('{ExpectedDateString}');\r\n" +
+ "App.call_test_method ('InvokeDateTimeOffset', [ dt ], 'a');"
+ );
+ // FIXME
+ Assert.Equal(ExpectedDateTime, HelperMarshal._dateTimeValue);
+ }
+
+ [Fact]
+ public static void MarshalDateTimeByValueAutomatic()
+ {
+ HelperMarshal._dateTimeValue = default(DateTime);
+ Runtime.InvokeJS(
+ $"var dt = new Date('{ExpectedDateString}');\r\n" +
+ "App.call_test_method ('InvokeDateTimeByValue', [ dt.valueOf() ], 'a');"
+ );
+ Assert.Equal(ExpectedDateTime, HelperMarshal._dateTimeValue);
+ }
+
+ [Fact]
+ public static void MarshalUri()
+ {
+ var expected = new System.Uri("https://www.example.com/");
+ HelperMarshal._uriValue = default(System.Uri);
+ Runtime.InvokeJS(
+ @"var uri = 'https://www.example.com/';
+ App.call_test_method ('InvokeUri', [ uri ], 'u');"
+ );
+ Assert.Equal(expected, HelperMarshal._uriValue);
+ }
+
+ [Fact]
+ public static void MarshalCustomClassAutomatic()
+ {
+ HelperMarshal._ccValue = new HelperMarshal.CustomClass ();
+ Runtime.InvokeJS(
+ "App.call_test_method ('InvokeCustomClass', [ 4.13 ], 'a');"
+ );
+ Assert.Equal(4.13, HelperMarshal._ccValue?.D);
+ }
+
+ [Fact]
+ public static void MarshalCustomStructAutomatic()
+ {
+ HelperMarshal._csValue = default(HelperMarshal.CustomStruct);
+ Runtime.InvokeJS(
+ "App.call_test_method ('InvokeCustomStruct', [ 4.13 ], 'a');"
+ );
+ Assert.Equal(4.13, HelperMarshal._csValue.D);
+ }
+
+ [Fact]
+ public static void MarshalCustomDateAutomatic()
+ {
+ HelperMarshal._dateTimeValue = default(DateTime);
+ Runtime.InvokeJS(
+ $"var dt = new Date('{ExpectedDateString}');\r\n" +
+ "App.call_test_method ('InvokeCustomDate', [ dt ], 'a');"
+ );
+ Assert.Equal(ExpectedDateTime, HelperMarshal._dateTimeValue);
+ }
+
+ [Fact]
+ public static void ReturnCustomClass()
+ {
+ HelperMarshal._ccValue = new HelperMarshal.CustomClass ();
+ Runtime.InvokeJS(
+ "var cc = App.call_test_method ('ReturnCustomClass', [ 4.13 ], 'a');" +
+ "App.call_test_method ('InvokeCustomClass', [ cc ], 'a');"
+ );
+ Assert.Equal(4.13, HelperMarshal._ccValue?.D);
+ }
+
+ [Fact]
+ public static void ReturnCustomStruct()
+ {
+ HelperMarshal._csValue = default(HelperMarshal.CustomStruct);
+ Runtime.InvokeJS(
+ "var cs = App.call_test_method ('ReturnCustomStruct', [ 4.13 ], 'a');" +
+ "App.call_test_method ('InvokeCustomStruct', [ cs ], 'a');"
+ );
+ Assert.Equal(4.13, HelperMarshal._csValue.D);
+ }
+
+ [Fact]
+ public static void ReturnCustomDate()
+ {
+ HelperMarshal._dateTimeValue = default(DateTime);
+ Runtime.InvokeJS(
+ $"var dt = new Date('{ExpectedDateString}');\r\n" +
+ "var cd = App.call_test_method ('ReturnCustomDate', [ dt ], 'a');" +
+ "App.call_test_method ('InvokeCustomDate', [ cd ], 'a');"
+ );
+ Assert.Equal(ExpectedDateTime, HelperMarshal._dateTimeValue);
+ }
+
+ [Fact]
+ public static void InvokeCustomVector3()
+ {
+ HelperMarshal._vec3Value = default(HelperMarshal.CustomVector3);
+ Runtime.InvokeJS(
+ "App.call_test_method ('InvokeCustomVector3', [ [1, 2.5, 4] ], 'a');"
+ );
+ Assert.Equal(1, HelperMarshal._vec3Value.X);
+ Assert.Equal(2.5, HelperMarshal._vec3Value.Y);
+ Assert.Equal(4, HelperMarshal._vec3Value.Z);
+ }
+
+ [Fact]
+ public static void ReturnCustomVector3()
+ {
+ HelperMarshal._vec3Value = default(HelperMarshal.CustomVector3);
+ Runtime.InvokeJS(
+ "var cv3 = App.call_test_method ('ReturnCustomVector3', [ [1, 2.5, 4] ], 'a');" +
+ "App.call_test_method ('InvokeCustomVector3', [ cv3 ], 'a');"
+ );
+ Assert.Equal(1, HelperMarshal._vec3Value.X);
+ Assert.Equal(2.5, HelperMarshal._vec3Value.Y);
+ Assert.Equal(4, HelperMarshal._vec3Value.Z);
+ }
+
+ [Fact]
+ public static void AddCustomVector3()
+ {
+ HelperMarshal._stringResource = null;
+ HelperMarshal._vec3Value = default(HelperMarshal.CustomVector3);
+ Runtime.InvokeJS(
+ "var cva = App.call_test_method ('MakeCustomVector3', [1, 2.5, 4], 'aaa');" +
+ "var cvb = App.call_test_method ('MakeCustomVector3', [4, 3, 2], 'aaa');" +
+ "var res = App.call_test_method ('AddCustomVector3', [ cva, cvb ], 'aa');" +
+ "App.call_test_method ('InvokeCustomVector3', [ res ], 'a');" +
+ "App.call_test_method ('InvokeString', [ String(res) ], 'a');"
+ );
+ Assert.Equal("5,5.5,6", HelperMarshal._stringResource);
+ Assert.Equal(5, HelperMarshal._vec3Value.X);
+ Assert.Equal(5.5, HelperMarshal._vec3Value.Y);
+ Assert.Equal(6, HelperMarshal._vec3Value.Z);
+ }
}
}
diff --git a/src/mono/mono/metadata/class-accessors.c b/src/mono/mono/metadata/class-accessors.c
index 76065cb45c456f..662c0493553ac9 100644
--- a/src/mono/mono/metadata/class-accessors.c
+++ b/src/mono/mono/metadata/class-accessors.c
@@ -67,7 +67,9 @@ mono_class_try_get_generic_class (MonoClass *klass)
guint32
mono_class_get_flags (MonoClass *klass)
{
- switch (m_class_get_class_kind (klass)) {
+ g_assert (klass);
+ guint32 kind = m_class_get_class_kind (klass);
+ switch (kind) {
case MONO_CLASS_DEF:
case MONO_CLASS_GTD:
return m_classdef_get_flags ((MonoClassDef*)klass);
diff --git a/src/mono/wasm/build/README.md b/src/mono/wasm/build/README.md
index faed3d05827f68..c6fb0a8681944b 100644
--- a/src/mono/wasm/build/README.md
+++ b/src/mono/wasm/build/README.md
@@ -132,3 +132,13 @@ them for the new task assembly.
- `eng/testing/linker/trimmingTests.targets`,
- `src/tests/Common/wasm-test-runner/WasmTestRunner.proj`
- `src/tests/Directory.Build.targets`
+
+## Profiling build performance
+
+If encountering build performance issues, you can use the rollup `--perf` option and the typescript compiler `--generateCpuProfile` option to get build profile data, like so:
+
+```../emsdk/node/14.15.5_64bit/bin/npm run rollup --perf -- --perf --environment Configuration:Release,NativeBinDir:./rollup-test-data,ProductVersion:12.3.4```
+
+```node node_modules/typescript/lib/tsc.js --generateCpuProfile dotnet-tsc.cpuprofile -p tsconfig.json ```
+
+The .cpuprofile file generated by node can be opened in the Performance tab of Chrome or Edge's devtools.
\ No newline at end of file
diff --git a/src/mono/wasm/build/WasmApp.targets b/src/mono/wasm/build/WasmApp.targets
index 145e5c8f7ee909..090290e516b63a 100644
--- a/src/mono/wasm/build/WasmApp.targets
+++ b/src/mono/wasm/build/WasmApp.targets
@@ -276,6 +276,13 @@
+
+
+
+
+
+
+
+ DebugLevel="$(WasmDebugLevel)"
+ MarshaledTypes="@(WasmMarshaledType)"
+ >
diff --git a/src/mono/wasm/runtime/corebindings.ts b/src/mono/wasm/runtime/corebindings.ts
index 8f64ad00dbaf85..88dc910e8ed82d 100644
--- a/src/mono/wasm/runtime/corebindings.ts
+++ b/src/mono/wasm/runtime/corebindings.ts
@@ -1,11 +1,11 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
-import { JSHandle, GCHandle, MonoObject } from "./types";
+import { JSHandle, GCHandle, MonoObject, MonoType, MonoMethod } from "./types";
import { PromiseControl } from "./cancelable-promise";
import { runtimeHelpers } from "./imports";
-const fn_signatures: [jsname: string, csname: string, signature: string/*ArgsMarshalString*/][] = [
+const fn_signatures: [jsname: string, csname: string, signature: string][] = [
["_get_cs_owned_object_by_js_handle", "GetCSOwnedObjectByJSHandle", "ii!"],
["_get_cs_owned_object_js_handle", "GetCSOwnedObjectJSHandle", "mi"],
["_try_get_cs_owned_object_js_handle", "TryGetCSOwnedObjectJSHandle", "mi"],
@@ -23,10 +23,10 @@ const fn_signatures: [jsname: string, csname: string, signature: string/*ArgsMar
["_setup_js_cont", "SetupJSContinuation", "mo"],
["_object_to_string", "ObjectToString", "m"],
- ["_get_date_value", "GetDateValue", "m"],
- ["_create_date_time", "CreateDateTime", "d!"],
- ["_create_uri", "CreateUri", "s!"],
["_is_simple_array", "IsSimpleArray", "m"],
+
+ ["make_marshal_signature_info", "MakeMarshalSignatureInfo", "ii"],
+ ["get_custom_marshaler_info", "GetCustomMarshalerInfoForType", "is"],
];
export interface t_CSwraps {
@@ -48,10 +48,12 @@ export interface t_CSwraps {
_setup_js_cont(task: MonoObject, continuation: PromiseControl): MonoObject
_object_to_string(obj: MonoObject): string;
- _get_date_value(obj: MonoObject): number;
- _create_date_time(ticks: number): MonoObject;
- _create_uri(uri: string): MonoObject;
_is_simple_array(obj: MonoObject): boolean;
+
+ make_marshal_signature_info(typePtr: MonoType, methodPtr: MonoMethod): string;
+ get_custom_marshaler_info(typePtr: MonoType, marshalerFullName: string | null): string;
+
+ generate_args_marshaler(signature: string, methodPtr: MonoMethod): string;
}
const wrapped_cs_functions: t_CSwraps = {};
diff --git a/src/mono/wasm/runtime/cs-to-js.ts b/src/mono/wasm/runtime/cs-to-js.ts
index 2a7646d85973fd..eb28a7942273cb 100644
--- a/src/mono/wasm/runtime/cs-to-js.ts
+++ b/src/mono/wasm/runtime/cs-to-js.ts
@@ -5,7 +5,7 @@ import { mono_wasm_new_root, WasmRoot } from "./roots";
import {
GCHandle, JSHandleDisposed, MonoArray,
MonoArrayNull, MonoObject, MonoObjectNull, MonoString,
- MonoType, MonoTypeNull
+ MonoType, MonoTypeNull, MarshalType, MarshalError
} from "./types";
import { runtimeHelpers } from "./imports";
import { conv_string } from "./strings";
@@ -16,50 +16,8 @@ import { mono_method_get_call_signature, call_method, wrap_error } from "./metho
import { _js_to_mono_obj } from "./js-to-cs";
import { _are_promises_supported, _create_cancelable_promise } from "./cancelable-promise";
import { getU32, getI32, getF32, getF64 } from "./memory";
-import { Int32Ptr, VoidPtr } from "./types/emscripten";
-
-// see src/mono/wasm/driver.c MARSHAL_TYPE_xxx and Runtime.cs MarshalType
-export enum MarshalType {
- NULL = 0,
- INT = 1,
- FP64 = 2,
- STRING = 3,
- VT = 4,
- DELEGATE = 5,
- TASK = 6,
- OBJECT = 7,
- BOOL = 8,
- ENUM = 9,
- URI = 22,
- SAFEHANDLE = 23,
- ARRAY_BYTE = 10,
- ARRAY_UBYTE = 11,
- ARRAY_UBYTE_C = 12,
- ARRAY_SHORT = 13,
- ARRAY_USHORT = 14,
- ARRAY_INT = 15,
- ARRAY_UINT = 16,
- ARRAY_FLOAT = 17,
- ARRAY_DOUBLE = 18,
- FP32 = 24,
- UINT32 = 25,
- INT64 = 26,
- UINT64 = 27,
- CHAR = 28,
- STRING_INTERNED = 29,
- VOID = 30,
- ENUM64 = 31,
- POINTER = 32
-}
-
-// see src/mono/wasm/driver.c MARSHAL_ERROR_xxx and Runtime.cs
-export enum MarshalError {
- BUFFER_TOO_SMALL = 512,
- NULL_CLASS_POINTER = 513,
- NULL_TYPE_POINTER = 514,
- UNSUPPORTED_TYPE = 515,
- FIRST = BUFFER_TOO_SMALL
-}
+import { extract_js_obj_root_with_converter, extract_js_obj_root_with_possible_converter } from "./custom-marshaler";
+import { VoidPtr, Int32Ptr } from "./types/emscripten";
const delegate_invoke_symbol = Symbol.for("wasm delegate_invoke");
const delegate_invoke_signature_symbol = Symbol.for("wasm delegate_invoke_signature");
@@ -84,8 +42,7 @@ function _unbox_cs_owned_root_as_js_object(root: WasmRoot) {
return js_obj;
}
-// eslint-disable-next-line @typescript-eslint/no-unused-vars
-function _unbox_mono_obj_root_with_known_nonprimitive_type_impl(root: WasmRoot, type: MarshalType, typePtr: MonoType, unbox_buffer: VoidPtr): any {
+function _unbox_mono_obj_root_with_known_nonprimitive_type_impl(root: WasmRoot, type: MarshalType, typePtr: MonoType, unbox_buffer: VoidPtr) : any {
//See MARSHAL_TYPE_ defines in driver.c
switch (type) {
case MarshalType.INT64:
@@ -96,13 +53,13 @@ function _unbox_mono_obj_root_with_known_nonprimitive_type_impl(root: WasmRoot20: // clr .NET DateTime
- return new Date(corebindings._get_date_value(root.value));
case 21: // clr .NET DateTimeOffset
- return corebindings._object_to_string(root.value);
+ throw new Error("Deprecated type (DATETIME / DATETIMEOFFSET)");
case MarshalType.URI:
return corebindings._object_to_string(root.value);
case MarshalType.SAFEHANDLE:
@@ -128,7 +84,7 @@ function _unbox_mono_obj_root_with_known_nonprimitive_type_impl(root: WasmRoot, type: MarshalType, unbox_buffer: VoidPtr): any {
+export function _unbox_mono_obj_root_with_known_nonprimitive_type(root: WasmRoot, type: MarshalType, unbox_buffer: VoidPtr) : any {
if (type >= MarshalError.FIRST)
throw new Error(`Got marshaling error ${type} when attempting to unbox object at address ${root.value} (root located at ${root.get_address()})`);
@@ -142,7 +98,7 @@ export function _unbox_mono_obj_root_with_known_nonprimitive_type(root: WasmRoot
return _unbox_mono_obj_root_with_known_nonprimitive_type_impl(root, type, typePtr, unbox_buffer);
}
-export function _unbox_mono_obj_root(root: WasmRoot): any {
+export function _unbox_mono_obj_root(root: WasmRoot) : any {
if (root.value === 0)
return undefined;
diff --git a/src/mono/wasm/runtime/custom-marshaler.ts b/src/mono/wasm/runtime/custom-marshaler.ts
new file mode 100644
index 00000000000000..c195b6b016bc38
--- /dev/null
+++ b/src/mono/wasm/runtime/custom-marshaler.ts
@@ -0,0 +1,371 @@
+import { Module, MONO, BINDING, runtimeHelpers } from "./imports";
+import cwraps from "./cwraps";
+import { WasmRoot } from "./roots";
+import {
+ MonoMethod, MonoObject, MonoObjectNull,
+ MonoType, MonoTypeNull,
+ MarshalType, MarshalTypeRecord, CustomMarshalerInfo
+} from "./types";
+import {
+ mono_bind_method, _create_named_function,
+ _get_type_aqn, _get_type_name,
+ get_method_signature_info, bindings_named_closures,
+ TypeConverter
+} from "./method-binding";
+import {
+ temp_malloc, _create_temp_frame, _release_temp_frame,
+ getI8, getI16, getI32, getI64,
+ getU8, getU16, getU32,
+ getF32, getF64,
+ setI8, setI16, setI32, setI64,
+ setU8, setU16, setU32,
+ setF32, setF64,
+} from "./memory";
+import { _unbox_ref_type_root_as_js_object } from "./cs-to-js";
+import { js_to_mono_obj } from "./js-to-cs";
+import cswraps from "./corebindings";
+import { VoidPtr } from "./types/emscripten";
+
+const _custom_marshaler_info_cache = new Map();
+const _struct_unboxer_cache = new Map();
+const _automatic_converter_table = new Map();
+export const _custom_marshaler_name_table : { [key: string] : string } = {};
+const _temp_unbox_buffer_cache = new Map();
+let _has_logged_custom_marshaler_table = false;
+
+function extract_js_obj_root_with_converter_impl (root : WasmRoot, typePtr : MonoType, unbox_buffer : VoidPtr, optional: boolean) : any {
+ if (root.value === MonoObjectNull)
+ return null;
+
+ const converter = _get_struct_unboxer_for_type (typePtr);
+
+ if (converter) {
+ let buffer_is_temporary = false;
+ if (!unbox_buffer) {
+ buffer_is_temporary = true;
+ if (_temp_unbox_buffer_cache.has(typePtr)) {
+ unbox_buffer = _temp_unbox_buffer_cache.get(typePtr);
+ _temp_unbox_buffer_cache.delete(typePtr);
+ } else {
+ unbox_buffer = Module._malloc(runtimeHelpers._unbox_buffer_size);
+ }
+ // TODO: Verify the MarshalType return value?
+ cwraps.mono_wasm_try_unbox_primitive_and_get_type(root.value, unbox_buffer, runtimeHelpers._unbox_buffer_size);
+ }
+ const objectSize = getI32(unbox_buffer + 4);
+ const pUnboxedData = unbox_buffer + 8;
+ _create_temp_frame();
+ try {
+ // Reftypes have no size because they cannot be copied into the unbox buffer,
+ // so we pass their managed address directly to the converter
+ if (objectSize <= 0)
+ return converter(root.value);
+ else
+ return converter(pUnboxedData);
+ } finally {
+ _release_temp_frame();
+ if (buffer_is_temporary) {
+ if (_temp_unbox_buffer_cache.has(typePtr))
+ Module._free(unbox_buffer);
+ else
+ _temp_unbox_buffer_cache.set(typePtr, unbox_buffer);
+ }
+ }
+ } else if (optional)
+ return _unbox_ref_type_root_as_js_object (root);
+ else
+ throw new Error (`No CustomJavaScriptMarshaler found for type ${_get_type_name(typePtr)}`);
+}
+
+// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
+export function extract_js_obj_root_with_converter (root : WasmRoot, typePtr : MonoType, unbox_buffer : VoidPtr) : any {
+ return extract_js_obj_root_with_converter_impl(root, typePtr, unbox_buffer, false);
+}
+
+// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
+export function extract_js_obj_root_with_possible_converter (root : WasmRoot, typePtr : MonoType, unbox_buffer : VoidPtr) : any {
+ return extract_js_obj_root_with_converter_impl(root, typePtr, unbox_buffer, true);
+}
+
+// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
+export function box_js_obj_with_converter (js_obj : any, typePtr : MonoType) : MonoObject {
+ if ((js_obj === null) || (js_obj === undefined))
+ return MonoObjectNull;
+
+ if (!typePtr)
+ throw new Error("No type pointer provided");
+
+ const converter = _pick_automatic_converter_for_type(typePtr);
+ if (!converter)
+ throw new Error (`No CustomJavaScriptMarshaler found for type ${_get_type_name(typePtr)}`);
+
+ _create_temp_frame();
+ try {
+ return converter(js_obj);
+ } finally {
+ _release_temp_frame();
+ }
+}
+
+// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
+function _create_interchange_closure (typePtr : MonoType) : any {
+ return {
+ // Put binding/mono API namespaces in the closure so that interchange filters can use them
+ Module,
+ MONO,
+ BINDING,
+ // RuntimeTypeHandle for the type so that type-oriented APIs can be used easily
+ typePtr,
+ // Special interchange-only API for temporary allocations
+ alloca: temp_malloc,
+ // Memory accessors
+ getI8, getI16, getI32, getI64,
+ getU8, getU16, getU32,
+ getF32, getF64,
+ setI8, setI16, setI32, setI64,
+ setU8, setU16, setU32,
+ setF32, setF64,
+ };
+}
+
+function _compile_interchange_to_js (typePtr : MonoType, boundConverter : Function, js : string | undefined, info : CustomMarshalerInfo) : Function {
+ if (!js)
+ return boundConverter;
+
+ const closure = _create_interchange_closure(typePtr);
+ const hasScratchBuffer = (info.scratchBufferSize || 0) > 0;
+
+ let converterKey = boundConverter.name || "boundConverter";
+ if (converterKey in closure)
+ converterKey += "_";
+ closure[converterKey] = boundConverter;
+
+ const filterParams = hasScratchBuffer
+ ? ["buffer", "bufferSize"]
+ : ["value"];
+
+ const filterName = "interchange_to_js_filter_for_type" + typePtr;
+
+ const filterExpression = _create_named_function(
+ filterName, filterParams, js, closure
+ );
+ closure[filterName] = filterExpression;
+
+ let bodyJs : string;
+ if (hasScratchBuffer) {
+ bodyJs = `let buffer = alloca(${info.scratchBufferSize});\r\n` +
+ `${converterKey}(value, [buffer, ${info.scratchBufferSize}]);\r\n` +
+ `let filteredValue = ${filterName}(buffer, ${info.scratchBufferSize});\r\n` +
+ "return filteredValue;";
+ } else {
+ bodyJs = `let convertedValue = ${converterKey}(value), filteredValue = ${filterName}(convertedValue);\r\n` +
+ "return filteredValue;";
+ }
+ const functionName = "interchange_to_js_for_type" + typePtr;
+ const result = _create_named_function(
+ functionName, ["value"], bodyJs, closure
+ );
+
+ return result;
+}
+
+function _get_custom_marshaler_info_for_type (typePtr : MonoType) : CustomMarshalerInfo | null {
+ if (!typePtr)
+ return null;
+ if (!_custom_marshaler_name_table)
+ return null;
+
+ let result;
+ if (!_custom_marshaler_info_cache.has (typePtr)) {
+ const aqn = _get_type_aqn (typePtr);
+ if (!aqn.startsWith("System.Object, System.Private.CoreLib, ")) {
+ let marshalerAQN = _custom_marshaler_name_table[aqn];
+ if (!marshalerAQN) {
+ for (const k in _custom_marshaler_name_table) {
+ // Perform a loose match against the assembly-qualified type names,
+ // because in some cases it is not possible or convenient to
+ // include the full string (i.e. version, culture, etc)
+ const isMatch = k.startsWith(aqn) || aqn.startsWith(k);
+ if (isMatch) {
+ marshalerAQN = _custom_marshaler_name_table[k];
+ break;
+ }
+ }
+ }
+
+ if (!marshalerAQN) {
+ if (!_has_logged_custom_marshaler_table) {
+ _has_logged_custom_marshaler_table = true;
+ console.log(`WARNING: Type "${aqn}" has no registered custom marshaler. A dump of the marshaler table follows:`);
+ for (const k in _custom_marshaler_name_table)
+ console.log(` ${k}: ${_custom_marshaler_name_table[k]}`);
+ }
+ _custom_marshaler_info_cache.set(typePtr, null);
+ return null;
+ }
+ const json = cswraps.get_custom_marshaler_info (typePtr, marshalerAQN);
+ result = JSON.parse(json);
+ if (!result)
+ throw new Error (`Configured custom marshaler for ${aqn} could not be loaded: ${marshalerAQN}`);
+ } else {
+ result = null;
+ }
+
+ _custom_marshaler_info_cache.set (typePtr, result);
+ } else {
+ result = _custom_marshaler_info_cache.get (typePtr);
+ }
+
+ return result || null;
+}
+
+function _get_struct_unboxer_for_type (typePtr : MonoType) : Function | null {
+ if (!typePtr)
+ throw new Error("no type");
+
+ if (!_struct_unboxer_cache.has (typePtr)) {
+ const info = _get_custom_marshaler_info_for_type (typePtr);
+ if (!info) {
+ _struct_unboxer_cache.set (typePtr, null);
+ return null;
+ }
+
+ if (info.error)
+ console.error(`Error while configuring automatic converter for type ${_get_type_name(typePtr)}: ${info.error}`);
+
+ const interchangeToJs = info.interchangeToJs;
+
+ const convMethod = info.outputPtr;
+ if (!convMethod) {
+ if (info.typePtr)
+ console.error(`Automatic converter for type ${_get_type_name(typePtr)} has no suitable ToJavaScript method`);
+ // We explicitly store null in the cache so that lookups are not performed again for this type
+ _struct_unboxer_cache.set (typePtr, null);
+ } else {
+ const typeName = _get_type_name(typePtr);
+ const signature = (info.scratchBufferSize || 0) > 0
+ ? "mb"
+ : "m";
+ const boundConverter = mono_bind_method (
+ convMethod, null, signature, typeName + "$ToJavaScript"
+ );
+
+ _struct_unboxer_cache.set (typePtr, _compile_interchange_to_js (typePtr, boundConverter, interchangeToJs, info));
+ }
+ }
+
+ return _struct_unboxer_cache.get (typePtr) || null;
+}
+
+function _compile_js_to_interchange (typePtr : MonoType, boundConverter : Function, js : string | undefined, info : CustomMarshalerInfo) : Function {
+ if (!js)
+ return boundConverter;
+
+ const closure = _create_interchange_closure(typePtr);
+ const hasScratchBuffer = (info.scratchBufferSize || 0) > 0;
+
+ let converterKey = boundConverter.name || "boundConverter";
+ if (converterKey in closure)
+ converterKey += "_";
+ closure[converterKey] = boundConverter;
+
+ const filterParams = hasScratchBuffer
+ ? ["value", "buffer", "bufferSize"]
+ : ["value"];
+
+ const filterName = "js_to_interchange_filter_for_type" + typePtr;
+ const filterExpression = _create_named_function(
+ filterName, filterParams, js, closure
+ );
+
+ closure[filterName] = filterExpression;
+ const functionName = "js_to_interchange_for_type" + typePtr;
+
+ let bodyJs : string;
+ if (hasScratchBuffer) {
+ bodyJs = `let buffer = alloca(${info.scratchBufferSize});\r\n` +
+ `${filterName}(value, buffer, ${info.scratchBufferSize});\r\n` +
+ `let span = [buffer, ${info.scratchBufferSize}];\r\n` +
+ `let convertedResult = ${converterKey}(span, method, parmIdx);\r\n` +
+ "return convertedResult;";
+ } else {
+ bodyJs = `let filteredValue = ${filterName}(value);\r\n` +
+ `let convertedResult = ${converterKey}(filteredValue, method, parmIdx);\r\n` +
+ "return convertedResult;";
+ }
+
+ const result = _create_named_function(
+ functionName,
+ ["value", "method", "parmIdx"], bodyJs, closure
+ );
+
+ return result;
+}
+
+export function _pick_automatic_converter_for_type (typePtr : MonoType) : Function | null {
+ if (!typePtr)
+ throw new Error("typePtr is null or undefined");
+
+ if (!_automatic_converter_table.has(typePtr)) {
+ let info = _get_custom_marshaler_info_for_type(typePtr);
+ // HACK
+ if (!info)
+ info = {};
+ if (info.error)
+ console.error(`Error while configuring automatic converter for type ${_get_type_name(typePtr)}: ${info.error}`);
+
+ const jsToInterchange = info.jsToInterchange;
+
+ const convMethod = info.inputPtr;
+ if (!convMethod) {
+ if (info.typePtr)
+ console.error(`Automatic converter for type ${_get_type_name(typePtr)} has no suitable FromJavaScript method`);
+ _automatic_converter_table.set(typePtr, null);
+ return null;
+ }
+
+ // FIXME
+ const sigInfo = get_method_signature_info(MonoTypeNull, convMethod);
+ if (sigInfo.parameters.length < 1)
+ throw new Error("Expected at least one parameter");
+ // Return unboxed so it can go directly into the arguments list
+ const signature = sigInfo.parameters[0].signatureChar + "!";
+ const methodName = _get_type_name(typePtr) + "$FromJavaScript";
+ const boundConverter = mono_bind_method(
+ convMethod, null, signature, methodName
+ );
+
+ const result = _compile_js_to_interchange(typePtr, boundConverter, jsToInterchange, info);
+
+ _automatic_converter_table.set(typePtr, result);
+ bindings_named_closures.set(`type${typePtr}`, result);
+ }
+
+ return _automatic_converter_table.get(typePtr) || null;
+}
+
+export function _pick_automatic_converter (methodPtr : MonoMethod, args_marshal : string, paramRecord : MarshalTypeRecord) : TypeConverter {
+ const needs_unbox = (paramRecord.marshalType === MarshalType.VT);
+
+ if (
+ (paramRecord.marshalType === MarshalType.VT) ||
+ (paramRecord.marshalType === MarshalType.OBJECT)
+ ) {
+ const res = _pick_automatic_converter_for_type (paramRecord.typePtr);
+ if (res) {
+ return {
+ convert: res,
+ needs_root: !needs_unbox,
+ needs_unbox
+ };
+ }
+ if (needs_unbox)
+ throw new Error(`found no automatic converter for type ${_get_type_name(paramRecord.typePtr)}`);
+ }
+
+ return {
+ convert: js_to_mono_obj,
+ needs_root: !needs_unbox,
+ needs_unbox
+ };
+}
\ No newline at end of file
diff --git a/src/mono/wasm/runtime/cwraps.ts b/src/mono/wasm/runtime/cwraps.ts
index 98dd8f8e4e9bb5..3351c8d67b85a1 100644
--- a/src/mono/wasm/runtime/cwraps.ts
+++ b/src/mono/wasm/runtime/cwraps.ts
@@ -57,6 +57,7 @@ const fn_signatures: [ident: string, returnType: string | null, argTypes?: strin
["mono_wasm_type_get_class", "number", ["number"]],
["mono_wasm_get_type_name", "string", ["number"]],
["mono_wasm_get_type_aqn", "string", ["number"]],
+ ["mono_wasm_get_class_for_bind_or_invoke", "number", ["number", "number"]],
["mono_wasm_unbox_rooted", "number", ["number"]],
//DOTNET
@@ -97,7 +98,7 @@ export interface t_Cwraps {
mono_wasm_find_corlib_type(namespace: string, name: string): MonoType;
mono_wasm_assembly_find_type(assembly: MonoAssembly, namespace: string, name: string): MonoType;
mono_wasm_assembly_find_method(klass: MonoClass, name: string, args: number): MonoMethod;
- mono_wasm_invoke_method(method: MonoMethod, this_arg: MonoObject, params: VoidPtr, out_exc: MonoObject): MonoObject;
+ mono_wasm_invoke_method(method: MonoMethod, this_arg: MonoObject, params: VoidPtr, out_exc: VoidPtr): MonoObject;
mono_wasm_string_get_utf8(str: MonoString): CharPtr;
mono_wasm_string_from_utf16(str: CharPtr, len: number): MonoString;
mono_wasm_get_obj_type(str: MonoObject): number;
@@ -117,6 +118,7 @@ export interface t_Cwraps {
mono_wasm_type_get_class(ty: MonoType): MonoClass;
mono_wasm_get_type_name(ty: MonoType): string;
mono_wasm_get_type_aqn(ty: MonoType): string;
+ mono_wasm_get_class_for_bind_or_invoke(this_arg: MonoObject, method: MonoMethod): MonoClass;
mono_wasm_unbox_rooted(obj: MonoObject): VoidPtr;
//DOTNET
diff --git a/src/mono/wasm/runtime/debug.ts b/src/mono/wasm/runtime/debug.ts
index 8eb60b46e5478e..2aa8f121a55b80 100644
--- a/src/mono/wasm/runtime/debug.ts
+++ b/src/mono/wasm/runtime/debug.ts
@@ -179,7 +179,7 @@ export function mono_wasm_call_function_on(request: CallRequest): CFOResponse {
const fn_args = request.arguments != undefined ? request.arguments.map(a => JSON.stringify(a.value)) : [];
- const fn_body_template = `var fn = ${request.functionDeclaration}; return fn.apply(proxy, [${fn_args}]);`;
+ const fn_body_template = `const fn = ${request.functionDeclaration}; return fn.apply(proxy, [${fn_args}]);`;
const fn_defn = new Function("proxy", fn_body_template);
const fn_res = fn_defn(proxy);
diff --git a/src/mono/wasm/runtime/dotnet.d.ts b/src/mono/wasm/runtime/dotnet.d.ts
index b939d337aab85f..8687c0769e4ae1 100644
--- a/src/mono/wasm/runtime/dotnet.d.ts
+++ b/src/mono/wasm/runtime/dotnet.d.ts
@@ -1,7 +1,7 @@
//! Licensed to the .NET Foundation under one or more agreements.
//! The .NET Foundation licenses this file to you under the MIT license.
-//!
-//! This is generated file, see src/mono/wasm/runtime/rollup.config.js
+//!
+//! This is generated file, see src/mono/wasm/runtime/rollup.config.js
declare interface ManagedPointer {
__brandManagedPointer: "ManagedPointer";
@@ -139,6 +139,9 @@ declare type MonoConfig = {
aot_profiler_options?: AOTProfilerOptions;
coverage_profiler_options?: CoverageProfilerOptions;
ignore_pdb_load_errors?: boolean;
+ custom_marshalers?: {
+ [key: string]: string | undefined;
+ };
};
declare type MonoConfigError = {
isError: true;
@@ -223,6 +226,7 @@ declare type DotnetModuleConfigImports = {
declare function mono_wasm_runtime_ready(): void;
declare function mono_wasm_setenv(name: string, value: string): void;
+declare function mono_wasm_register_custom_marshaler(aqn: string, marshalerAQN: string): void;
declare function mono_load_runtime_and_bcl_args(config: MonoConfig | MonoConfigError | undefined): Promise;
declare function mono_wasm_load_data_archive(data: Uint8Array, prefix: string): boolean;
/**
@@ -237,7 +241,7 @@ declare function mono_wasm_load_config(configFilePath: string): Promise;
declare function mono_wasm_load_icu_data(offset: VoidPtr): boolean;
declare function conv_string(mono_obj: MonoString): string | null;
-declare function js_string_to_mono_string(string: string): MonoString | null;
+declare function js_string_to_mono_string(string: string): MonoString;
declare function js_to_mono_obj(js_obj: any): MonoObject;
declare function js_typed_array_to_array(js_obj: any): MonoArray;
@@ -250,25 +254,26 @@ declare function mono_call_assembly_entry_point(assembly: string, args?: any[],
declare function mono_wasm_load_bytes_into_heap(bytes: Uint8Array): VoidPtr;
-declare type _MemOffset = number | VoidPtr | NativePointer;
-declare function setU8(offset: _MemOffset, value: number): void;
-declare function setU16(offset: _MemOffset, value: number): void;
-declare function setU32(offset: _MemOffset, value: number): void;
-declare function setI8(offset: _MemOffset, value: number): void;
-declare function setI16(offset: _MemOffset, value: number): void;
-declare function setI32(offset: _MemOffset, value: number): void;
-declare function setI64(offset: _MemOffset, value: number): void;
-declare function setF32(offset: _MemOffset, value: number): void;
-declare function setF64(offset: _MemOffset, value: number): void;
-declare function getU8(offset: _MemOffset): number;
-declare function getU16(offset: _MemOffset): number;
-declare function getU32(offset: _MemOffset): number;
-declare function getI8(offset: _MemOffset): number;
-declare function getI16(offset: _MemOffset): number;
-declare function getI32(offset: _MemOffset): number;
-declare function getI64(offset: _MemOffset): number;
-declare function getF32(offset: _MemOffset): number;
-declare function getF64(offset: _MemOffset): number;
+declare type DotnetMemOffset = number | NativePointer;
+declare type DotnetMemValue = number | NativePointer | ManagedPointer;
+declare function setU8(offset: DotnetMemOffset, value: number): void;
+declare function setU16(offset: DotnetMemOffset, value: number): void;
+declare function setU32(offset: DotnetMemOffset, value: DotnetMemValue): void;
+declare function setI8(offset: DotnetMemOffset, value: number): void;
+declare function setI16(offset: DotnetMemOffset, value: number): void;
+declare function setI32(offset: DotnetMemOffset, value: number): void;
+declare function setI64(offset: DotnetMemOffset, value: number): void;
+declare function setF32(offset: DotnetMemOffset, value: number): void;
+declare function setF64(offset: DotnetMemOffset, value: number): void;
+declare function getU8(offset: DotnetMemOffset): number;
+declare function getU16(offset: DotnetMemOffset): number;
+declare function getU32(offset: DotnetMemOffset): number;
+declare function getI8(offset: DotnetMemOffset): number;
+declare function getI16(offset: DotnetMemOffset): number;
+declare function getI32(offset: DotnetMemOffset): number;
+declare function getI64(offset: DotnetMemOffset): number;
+declare function getF32(offset: DotnetMemOffset): number;
+declare function getF64(offset: DotnetMemOffset): number;
declare function mono_run_main_and_exit(main_assembly_name: string, args: string[]): Promise;
declare function mono_run_main(main_assembly_name: string, args: string[]): Promise;
@@ -286,6 +291,7 @@ declare const MONO: {
mono_wasm_release_roots: typeof mono_wasm_release_roots;
mono_run_main: typeof mono_run_main;
mono_run_main_and_exit: typeof mono_run_main_and_exit;
+ mono_wasm_register_custom_marshaler: typeof mono_wasm_register_custom_marshaler;
mono_wasm_add_assembly: (name: string, data: VoidPtr, size: number) => number;
mono_wasm_load_runtime: (unused: string, debug_level: number) => void;
config: MonoConfig | MonoConfigError;
diff --git a/src/mono/wasm/runtime/driver.c b/src/mono/wasm/runtime/driver.c
index dbe9800b9f12de..425d39fa1b7a4e 100644
--- a/src/mono/wasm/runtime/driver.c
+++ b/src/mono/wasm/runtime/driver.c
@@ -90,21 +90,21 @@ char *mono_method_get_full_name (MonoMethod *method);
#define MARSHAL_TYPE_VOID 30
#define MARSHAL_TYPE_POINTER 32
+// Used for passing spans to C# from the JS bindings. Since spans have type restrictions,
+// no boxed value will ever have this type and driver.c does not ever produce it
+#define MARSHAL_TYPE_SPAN_BYTE 33
+
// errors
#define MARSHAL_ERROR_BUFFER_TOO_SMALL 512
#define MARSHAL_ERROR_NULL_CLASS_POINTER 513
#define MARSHAL_ERROR_NULL_TYPE_POINTER 514
-static MonoClass* datetime_class;
-static MonoClass* datetimeoffset_class;
static MonoClass* uri_class;
static MonoClass* task_class;
static MonoClass* safehandle_class;
static MonoClass* voidtaskresult_class;
-static int resolved_datetime_class = 0,
- resolved_datetimeoffset_class = 0,
- resolved_uri_class = 0,
+static int resolved_uri_class = 0,
resolved_task_class = 0,
resolved_safehandle_class = 0,
resolved_voidtaskresult_class = 0;
@@ -158,7 +158,7 @@ mono_wasm_register_root (char *start, size_t size, const char *name)
return mono_gc_register_root (start, size, (MonoGCDescriptor)NULL, MONO_ROOT_SOURCE_EXTERNAL, NULL, name ? name : "mono_wasm_register_root");
}
-EMSCRIPTEN_KEEPALIVE void
+EMSCRIPTEN_KEEPALIVE void
mono_wasm_deregister_root (char *addr)
{
mono_gc_deregister_root (addr);
@@ -575,7 +575,7 @@ mono_wasm_assembly_load (const char *name)
return res;
}
-EMSCRIPTEN_KEEPALIVE MonoAssembly*
+EMSCRIPTEN_KEEPALIVE MonoAssembly*
mono_wasm_get_corlib ()
{
return mono_image_get_assembly (mono_get_corlib());
@@ -656,7 +656,7 @@ mono_wasm_assembly_get_entry_point (MonoAssembly *assembly)
uint32_t entry = mono_image_get_entry_point (image);
if (!entry)
return NULL;
-
+
mono_domain_ensure_entry_assembly (root_domain, assembly);
method = mono_get_method (image, entry, NULL);
@@ -752,14 +752,6 @@ MonoClass* mono_get_uri_class(MonoException** exc)
void mono_wasm_ensure_classes_resolved ()
{
- if (!datetime_class && !resolved_datetime_class) {
- datetime_class = mono_class_from_name (mono_get_corlib(), "System", "DateTime");
- resolved_datetime_class = 1;
- }
- if (!datetimeoffset_class && !resolved_datetimeoffset_class) {
- datetimeoffset_class = mono_class_from_name (mono_get_corlib(), "System", "DateTimeOffset");
- resolved_datetimeoffset_class = 1;
- }
if (!uri_class && !resolved_uri_class) {
MonoException** exc = NULL;
uri_class = mono_get_uri_class(exc);
@@ -784,7 +776,8 @@ mono_wasm_marshal_type_from_mono_type (int mono_type, MonoClass *klass, MonoType
return MARSHAL_TYPE_VOID;
case MONO_TYPE_BOOLEAN:
return MARSHAL_TYPE_BOOL;
- case MONO_TYPE_I: // IntPtr
+ case MONO_TYPE_I: // IntPtr
+ case MONO_TYPE_U: // UIntPtr
case MONO_TYPE_PTR:
return MARSHAL_TYPE_POINTER;
case MONO_TYPE_I1:
@@ -810,29 +803,29 @@ mono_wasm_marshal_type_from_mono_type (int mono_type, MonoClass *klass, MonoType
return MARSHAL_TYPE_STRING;
case MONO_TYPE_SZARRAY: { // simple zero based one-dim-array
if (klass) {
- 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;
- }
+ 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;
+ }
} else {
return MARSHAL_TYPE_OBJECT;
}
@@ -841,24 +834,20 @@ mono_wasm_marshal_type_from_mono_type (int mono_type, MonoClass *klass, MonoType
mono_wasm_ensure_classes_resolved ();
if (klass) {
- 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 (klass == voidtaskresult_class)
- return MARSHAL_TYPE_VOID;
- if (mono_class_is_enum (klass))
- return MARSHAL_TYPE_ENUM;
+ if (uri_class && mono_class_is_assignable_from(uri_class, klass))
+ return MARSHAL_TYPE_URI;
+ if (klass == voidtaskresult_class)
+ return MARSHAL_TYPE_VOID;
+ if (mono_class_is_enum (klass))
+ return MARSHAL_TYPE_ENUM;
if (type && !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_VT;
+ if (mono_class_is_delegate (klass))
+ return MARSHAL_TYPE_DELEGATE;
+ if (class_is_task(klass))
+ return MARSHAL_TYPE_TASK;
if (safehandle_class && (klass == safehandle_class || mono_class_is_subclass_of(klass, safehandle_class, 0)))
- return MARSHAL_TYPE_SAFEHANDLE;
+ return MARSHAL_TYPE_SAFEHANDLE;
}
return MARSHAL_TYPE_OBJECT;
@@ -929,7 +918,7 @@ mono_wasm_try_unbox_primitive_and_get_type (MonoObject *obj, void *result, int r
MonoType *type = mono_class_get_type (klass), *original_type = type;
if (!type)
return MARSHAL_ERROR_NULL_TYPE_POINTER;
-
+
if ((klass == mono_get_string_class ()) &&
mono_string_instance_is_interned ((MonoString *)obj)) {
*resultL = 0;
@@ -939,19 +928,29 @@ mono_wasm_try_unbox_primitive_and_get_type (MonoObject *obj, void *result, int r
if (mono_class_is_enum (klass))
type = mono_type_get_underlying_type (type);
-
+
if (!type)
return MARSHAL_ERROR_NULL_TYPE_POINTER;
-
+
+ if (!type)
+ return MARSHAL_ERROR_NULL_TYPE_POINTER;
+
int mono_type = mono_type_get_type (type);
-
+
+ if (mono_type == MONO_TYPE_GENERICINST) {
+ // HACK: While the 'any other type' fallback is valid for classes, it will do the
+ // wrong thing for structs, so we need to make sure the valuetype handler is used
+ if (mono_type_generic_inst_is_valuetype (type))
+ mono_type = MONO_TYPE_VALUETYPE;
+ }
+
if (mono_type == MONO_TYPE_GENERICINST) {
- // HACK: While the 'any other type' fallback is valid for classes, it will do the
+ // HACK: While the 'any other type' fallback is valid for classes, it will do the
// wrong thing for structs, so we need to make sure the valuetype handler is used
if (mono_type_generic_inst_is_valuetype (type))
mono_type = MONO_TYPE_VALUETYPE;
}
-
+
// FIXME: We would prefer to unbox once here but it will fail if the value isn't unboxable
switch (mono_type) {
@@ -994,7 +993,7 @@ mono_wasm_try_unbox_primitive_and_get_type (MonoObject *obj, void *result, int r
break;
case MONO_TYPE_VALUETYPE:
{
- int obj_size = mono_object_get_size (obj),
+ int obj_size = mono_object_get_size (obj),
required_size = (sizeof (int)) + (sizeof (MonoType *)) + obj_size;
// Check whether this struct has special-case marshaling
@@ -1104,7 +1103,7 @@ mono_wasm_enable_on_demand_gc (int enable)
}
EMSCRIPTEN_KEEPALIVE MonoString *
-mono_wasm_intern_string (MonoString *string)
+mono_wasm_intern_string (MonoString *string)
{
return mono_string_intern (string);
}
@@ -1156,12 +1155,22 @@ mono_wasm_unbox_rooted (MonoObject *obj)
return mono_object_unbox (obj);
}
-EMSCRIPTEN_KEEPALIVE char *
+EMSCRIPTEN_KEEPALIVE MonoClass *
+mono_wasm_get_class_for_bind_or_invoke (MonoObject *this_arg, MonoMethod *method) {
+ if (this_arg)
+ return mono_object_get_class (this_arg);
+ else if (method)
+ return mono_method_get_class (method);
+ else
+ return NULL;
+}
+
+EMSCRIPTEN_KEEPALIVE char *
mono_wasm_get_type_name (MonoType * typePtr) {
return mono_type_get_name_full (typePtr, MONO_TYPE_NAME_FORMAT_REFLECTION);
}
-EMSCRIPTEN_KEEPALIVE char *
+EMSCRIPTEN_KEEPALIVE char *
mono_wasm_get_type_aqn (MonoType * typePtr) {
return mono_type_get_name_full (typePtr, MONO_TYPE_NAME_FORMAT_ASSEMBLY_QUALIFIED);
}
diff --git a/src/mono/wasm/runtime/exports.ts b/src/mono/wasm/runtime/exports.ts
index d960519e9b072b..d734abadb4fee6 100644
--- a/src/mono/wasm/runtime/exports.ts
+++ b/src/mono/wasm/runtime/exports.ts
@@ -33,7 +33,8 @@ import {
mono_wasm_load_data_archive, mono_wasm_asm_loaded,
mono_wasm_pre_init,
mono_wasm_runtime_is_initialized,
- mono_wasm_on_runtime_initialized
+ mono_wasm_on_runtime_initialized,
+ mono_wasm_register_custom_marshaler
} from "./startup";
import { mono_set_timeout, schedule_background_exec } from "./scheduling";
import { mono_wasm_load_icu_data, mono_wasm_get_icudt_name } from "./icu";
@@ -49,7 +50,7 @@ import {
mono_wasm_get_by_index, mono_wasm_get_global_object, mono_wasm_get_object_property,
mono_wasm_invoke_js,
mono_wasm_invoke_js_blazor,
- mono_wasm_invoke_js_with_args, mono_wasm_set_by_index, mono_wasm_set_object_property
+ mono_wasm_invoke_js_with_args, mono_wasm_set_by_index, mono_wasm_set_object_property,
} from "./method-calls";
import { mono_wasm_typed_array_copy_to, mono_wasm_typed_array_from, mono_wasm_typed_array_copy_from, mono_wasm_load_bytes_into_heap } from "./buffers";
import { mono_wasm_cancel_promise } from "./cancelable-promise";
@@ -83,6 +84,8 @@ const MONO = {
mono_run_main,
mono_run_main_and_exit,
+ mono_wasm_register_custom_marshaler,
+
// for Blazor's future!
mono_wasm_add_assembly: cwraps.mono_wasm_add_assembly,
mono_wasm_load_runtime: cwraps.mono_wasm_load_runtime,
@@ -218,7 +221,7 @@ function initializeImportsAndExports(
// backward compatibility
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
- module.mono_bind_static_method = (fqn: string, signature: string/*ArgsMarshalString*/): Function => {
+ module.mono_bind_static_method = (fqn: string, signature: string): Function => {
console.warn("Module.mono_bind_static_method is obsolete, please use BINDING.bind_static_method instead");
return mono_bind_static_method(fqn, signature);
};
@@ -266,7 +269,7 @@ function initializeImportsAndExports(
// if onRuntimeInitialized is set it's probably Blazor, we let them to do their own init sequence
if (!module.onRuntimeInitialized) {
- // note this would keep running in async-parallel with emscripten's `run()` and `postRun()`
+ // note this would keep running in async-parallel with emscripten's `run()` and `postRun()`
// because it's loading files asynchronously and the emscripten is not awaiting onRuntimeInitialized
// execution order == [1] ==
module.onRuntimeInitialized = () => mono_wasm_on_runtime_initialized();
diff --git a/src/mono/wasm/runtime/js-to-cs.ts b/src/mono/wasm/runtime/js-to-cs.ts
index 8bafaec669cd8f..78f1603dfd4a12 100644
--- a/src/mono/wasm/runtime/js-to-cs.ts
+++ b/src/mono/wasm/runtime/js-to-cs.ts
@@ -17,6 +17,8 @@ import { has_backing_array_buffer } from "./buffers";
import { JSHandle, MonoArray, MonoMethod, MonoObject, MonoObjectNull, MonoString, wasm_type_symbol } from "./types";
import { setI32, setU32, setF64 } from "./memory";
import { Int32Ptr, TypedArray } from "./types/emscripten";
+import { box_js_obj_with_converter } from "./custom-marshaler";
+import { find_corlib_type, find_type_in_assembly } from "./class-loader";
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
export function _js_to_mono_uri(should_add_in_flight: boolean, js_obj: any): MonoObject {
@@ -26,12 +28,11 @@ export function _js_to_mono_uri(should_add_in_flight: boolean, js_obj: any): Mon
return MonoObjectNull;
case typeof js_obj === "symbol":
case typeof js_obj === "string":
- return corebindings._create_uri(js_obj);
+ return box_js_obj_with_converter(js_obj, find_type_in_assembly ("System.Private.Uri", "System", "Uri", true));
default:
return _extract_mono_obj(should_add_in_flight, js_obj);
}
}
-
// this is only used from Blazor
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
export function js_to_mono_obj(js_obj: any): MonoObject {
@@ -70,7 +71,7 @@ export function _js_to_mono_obj(should_add_in_flight: boolean, js_obj: any): Mon
}
case js_obj.constructor.name === "Date":
// getTime() is always UTC
- return corebindings._create_date_time(js_obj.getTime());
+ return box_js_obj_with_converter(js_obj, find_corlib_type("System", "DateTime", true));
default:
return _extract_mono_obj(should_add_in_flight, js_obj);
}
@@ -203,7 +204,7 @@ export function _wrap_js_thenable_as_task(thenable: Promise): {
// ideally, this should be hold alive by lifespan of the resulting C# Task, but this is good cheap aproximation
const thenable_js_handle = mono_wasm_get_js_handle(thenable);
- // Note that we do not implement promise/task roundtrip.
+ // Note that we do not implement promise/task roundtrip.
// With more complexity we could recover original instance when this Task is marshaled back to JS.
// TODO optimization: return the tcs.Task on this same call instead of _get_tcs_task
const tcs_gc_handle = corebindings._create_tcs();
diff --git a/src/mono/wasm/runtime/memory.ts b/src/mono/wasm/runtime/memory.ts
index e8a89bd11a574a..85677d0e9e8a57 100644
--- a/src/mono/wasm/runtime/memory.ts
+++ b/src/mono/wasm/runtime/memory.ts
@@ -1,108 +1,116 @@
import { Module } from "./imports";
-import { VoidPtr, NativePointer } from "./types/emscripten";
+import { VoidPtr, NativePointer, ManagedPointer } from "./types/emscripten";
-const _temp_mallocs: Array | null> = [];
+const alloca_stack: Array = [];
+const alloca_buffer_size = 32 * 1024;
+let alloca_base: VoidPtr, alloca_offset: VoidPtr, alloca_limit: VoidPtr;
+
+function _ensure_allocated(): void {
+ if (alloca_base)
+ return;
+ alloca_base = Module._malloc(alloca_buffer_size);
+ alloca_offset = alloca_base;
+ alloca_limit = (alloca_base + alloca_buffer_size);
+}
export function temp_malloc(size: number): VoidPtr {
- if (!_temp_mallocs || !_temp_mallocs.length)
+ _ensure_allocated();
+ if (!alloca_stack.length)
throw new Error("No temp frames have been created at this point");
- const frame = _temp_mallocs[_temp_mallocs.length - 1] || [];
- const result = Module._malloc(size);
- frame.push(result);
- _temp_mallocs[_temp_mallocs.length - 1] = frame;
+ const result = alloca_offset;
+ alloca_offset += size;
+ if (alloca_offset >= alloca_limit)
+ throw new Error("Out of temp storage space");
return result;
}
export function _create_temp_frame(): void {
- _temp_mallocs.push(null);
+ _ensure_allocated();
+ alloca_stack.push(alloca_offset);
}
export function _release_temp_frame(): void {
- if (!_temp_mallocs.length)
+ if (!alloca_stack.length)
throw new Error("No temp frames have been created at this point");
- const frame = _temp_mallocs.pop();
- if (!frame)
- return;
-
- for (let i = 0, l = frame.length; i < l; i++)
- Module._free(frame[i]);
+ alloca_offset = alloca_stack.pop();
}
-type _MemOffset = number | VoidPtr | NativePointer;
+type DotnetMemOffset = number | NativePointer;
+type DotnetMemValue = number | NativePointer | ManagedPointer;
-export function setU8(offset: _MemOffset, value: number): void {
+export function setU8(offset: DotnetMemOffset, value: number): void {
Module.HEAPU8[offset] = value;
}
-export function setU16(offset: _MemOffset, value: number): void {
+export function setU16(offset: DotnetMemOffset, value: number): void {
Module.HEAPU16[offset >>> 1] = value;
}
-export function setU32(offset: _MemOffset, value: number): void {
- Module.HEAPU32[offset >>> 2] = value;
+export function setU32 (offset: DotnetMemOffset, value: DotnetMemValue) : void {
+ Module.HEAPU32[offset >>> 2] = value;
}
-export function setI8(offset: _MemOffset, value: number): void {
+export function setI8(offset: DotnetMemOffset, value: number): void {
Module.HEAP8[offset] = value;
}
-export function setI16(offset: _MemOffset, value: number): void {
+export function setI16(offset: DotnetMemOffset, value: number): void {
Module.HEAP16[offset >>> 1] = value;
}
-export function setI32(offset: _MemOffset, value: number): void {
+export function setI32(offset: DotnetMemOffset, value: number): void {
Module.HEAP32[offset >>> 2] = value;
}
// NOTE: Accepts a number, not a BigInt, so values over Number.MAX_SAFE_INTEGER will be corrupted
-export function setI64(offset: _MemOffset, value: number): void {
+export function setI64(offset: DotnetMemOffset, value: number): void {
Module.setValue(offset, value, "i64");
}
-export function setF32(offset: _MemOffset, value: number): void {
+export function setF32(offset: DotnetMemOffset, value: number): void {
Module.HEAPF32[offset >>> 2] = value;
}
-export function setF64(offset: _MemOffset, value: number): void {
+export function setF64(offset: DotnetMemOffset, value: number): void {
Module.HEAPF64[offset >>> 3] = value;
}
-export function getU8(offset: _MemOffset): number {
+export function getU8(offset: DotnetMemOffset): number {
return Module.HEAPU8[offset];
}
-export function getU16(offset: _MemOffset): number {
+export function getU16(offset: DotnetMemOffset): number {
return Module.HEAPU16[offset >>> 1];
}
-export function getU32(offset: _MemOffset): number {
+export function getU32(offset: DotnetMemOffset): number {
return Module.HEAPU32[offset >>> 2];
}
-export function getI8(offset: _MemOffset): number {
+export function getI8(offset: DotnetMemOffset): number {
return Module.HEAP8[offset];
}
-export function getI16(offset: _MemOffset): number {
+export function getI16(offset: DotnetMemOffset): number {
return Module.HEAP16[offset >>> 1];
}
-export function getI32(offset: _MemOffset): number {
+export function getI32(offset: DotnetMemOffset): number {
return Module.HEAP32[offset >>> 2];
}
// NOTE: Returns a number, not a BigInt. This means values over Number.MAX_SAFE_INTEGER will be corrupted
-export function getI64(offset: _MemOffset): number {
+export function getI64(offset: DotnetMemOffset): number {
return Module.getValue(offset, "i64");
}
-export function getF32(offset: _MemOffset): number {
+export function getF32(offset: DotnetMemOffset): number {
return Module.HEAPF32[offset >>> 2];
}
-export function getF64(offset: _MemOffset): number {
+export function getF64(offset: DotnetMemOffset): number {
return Module.HEAPF64[offset >>> 3];
}
diff --git a/src/mono/wasm/runtime/method-binding.ts b/src/mono/wasm/runtime/method-binding.ts
index 4827766f88c490..e58b75c1fda298 100644
--- a/src/mono/wasm/runtime/method-binding.ts
+++ b/src/mono/wasm/runtime/method-binding.ts
@@ -2,27 +2,36 @@
// The .NET Foundation licenses this file to you under the MIT license.
import { WasmRoot, WasmRootBuffer, mono_wasm_new_root } from "./roots";
-import { MonoClass, MonoMethod, MonoObject, coerceNull, VoidPtrNull, MonoType } from "./types";
-import { BINDING, Module, runtimeHelpers } from "./imports";
+import { Module, runtimeHelpers } from "./imports";
import { js_to_mono_enum, _js_to_mono_obj, _js_to_mono_uri } from "./js-to-cs";
-import { js_string_to_mono_string, js_string_to_mono_string_interned } from "./strings";
-import { MarshalType, _unbox_mono_obj_root_with_known_nonprimitive_type } from "./cs-to-js";
+import { _unbox_mono_obj_root_with_known_nonprimitive_type } from "./cs-to-js";
+import {
+ MonoClass, MonoMethod, MonoObject, coerceNull, MonoString, MonoObjectNull,
+ VoidPtrNull, MonoType, MarshalSignatureInfo, MonoTypeNull
+} from "./types";
+import { js_string_to_mono_string, js_string_to_mono_string_interned, conv_string } from "./strings";
import {
_create_temp_frame,
getI32, getU32, getF32, getF64,
setI32, setU32, setF32, setF64, setI64,
} from "./memory";
+import { _pick_automatic_converter_for_type } from "./custom-marshaler";
import {
_get_args_root_buffer_for_method_call, _get_buffer_for_method_call,
- _handle_exception_for_call, _teardown_after_call
+ _handle_exception_for_call, _teardown_after_call,
+ _convert_exception_for_method_call,
} from "./method-calls";
import cwraps from "./cwraps";
import { VoidPtr } from "./types/emscripten";
+import cswraps from "./corebindings";
-const primitiveConverters = new Map();
-const _signature_converters = new Map();
+const _signature_converters = new Map>();
const _method_descriptions = new Map();
+const _method_signature_info_table = new Map();
+const _bound_method_cache = new Map();
+export const bindings_named_closures = new Map();
+let bindings_named_closures_initialized = false;
export function _get_type_name(typePtr: MonoType): string {
if (!typePtr)
@@ -65,22 +74,13 @@ export function bind_runtime_method(method_name: string, signature: string): Fun
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
export function _create_named_function(name: string, argumentNames: string[], body: string, closure: any): Function {
- let result = null;
- let closureArgumentList: any[] | null = null;
let closureArgumentNames = null;
- if (closure) {
+ if (closure)
closureArgumentNames = Object.keys(closure);
- closureArgumentList = new Array(closureArgumentNames.length);
- for (let i = 0, l = closureArgumentNames.length; i < l; i++)
- closureArgumentList[i] = closure[closureArgumentNames[i]];
- }
const constructor = _create_rebindable_named_function(name, argumentNames, body, closureArgumentNames);
- // eslint-disable-next-line prefer-spread
- result = constructor.apply(null, closureArgumentList);
-
- return result;
+ return constructor(closure);
}
export function _create_rebindable_named_function(name: string, argumentNames: string[], body: string, closureArgNames: string[] | null): Function {
@@ -94,462 +94,284 @@ export function _create_rebindable_named_function(name: string, argumentNames: s
escapedFunctionIdentifier = "unnamed";
}
+ let closurePrefix = "";
+ if (closureArgNames) {
+ for (let i = 0; i < closureArgNames.length; i++) {
+ const argName = closureArgNames[i];
+ closurePrefix += `const ${argName} = __closure__.${argName};\r\n`;
+ }
+ closurePrefix += "\r\n";
+ }
+
+
let rawFunctionText = "function " + escapedFunctionIdentifier + "(" +
argumentNames.join(", ") +
- ") {\r\n" +
- body +
- "\r\n};\r\n";
+ ") {\r\n";
+
+ rawFunctionText += body + "\r\n";
const lineBreakRE = /\r(\n?)/g;
rawFunctionText =
- uriPrefix + strictPrefix +
+ uriPrefix + strictPrefix + closurePrefix +
rawFunctionText.replace(lineBreakRE, "\r\n ") +
- ` return ${escapedFunctionIdentifier};\r\n`;
-
- let result = null, keys = null;
+ `};\r\nreturn ${escapedFunctionIdentifier};\r\n`;
- if (closureArgNames) {
- keys = closureArgNames.concat([rawFunctionText]);
- } else {
- keys = [rawFunctionText];
- }
+ /*
+ console.log(rawFunctionText);
+ console.log("");
+ */
- result = Function.apply(Function, keys);
- return result;
-}
-
-export function _create_primitive_converters(): void {
- const result = primitiveConverters;
- result.set("m", { steps: [{}], size: 0 });
- result.set("s", { steps: [{ convert: js_string_to_mono_string.bind(BINDING) }], size: 0, needs_root: true });
- result.set("S", { steps: [{ convert: js_string_to_mono_string_interned.bind(BINDING) }], size: 0, needs_root: true });
- // note we also bind first argument to false for both _js_to_mono_obj and _js_to_mono_uri,
- // because we will root the reference, so we don't need in-flight reference
- // also as those are callback arguments and we don't have platform code which would release the in-flight reference on C# end
- result.set("o", { steps: [{ convert: _js_to_mono_obj.bind(BINDING, false) }], size: 0, needs_root: true });
- result.set("u", { steps: [{ convert: _js_to_mono_uri.bind(BINDING, false) }], size: 0, needs_root: true });
-
- // result.set ('k', { steps: [{ convert: js_to_mono_enum.bind (this), indirect: 'i64'}], size: 8});
- result.set("j", { steps: [{ convert: js_to_mono_enum.bind(BINDING), indirect: "i32" }], size: 8 });
-
- result.set("i", { steps: [{ indirect: "i32" }], size: 8 });
- result.set("l", { steps: [{ indirect: "i64" }], size: 8 });
- result.set("f", { steps: [{ indirect: "float" }], size: 8 });
- result.set("d", { steps: [{ indirect: "double" }], size: 8 });
+ return new Function("__closure__", rawFunctionText);
}
-function _create_converter_for_marshal_string(args_marshal: string/*ArgsMarshalString*/): Converter {
- const steps = [];
- let size = 0;
- let is_result_definitely_unmarshaled = false,
- is_result_possibly_unmarshaled = false,
- result_unmarshaled_if_argc = -1,
- needs_root_buffer = false;
-
- for (let i = 0; i < args_marshal.length; ++i) {
- const key = args_marshal[i];
-
- if (i === args_marshal.length - 1) {
- if (key === "!") {
- is_result_definitely_unmarshaled = true;
- continue;
- } else if (key === "m") {
- is_result_possibly_unmarshaled = true;
- result_unmarshaled_if_argc = args_marshal.length - 1;
- }
- } else if (key === "!")
- throw new Error("! must be at the end of the signature");
-
- const conv = primitiveConverters.get(key);
- if (!conv)
- throw new Error("Unknown parameter type " + key);
-
- const localStep = Object.create(conv.steps[0]);
- localStep.size = conv.size;
- if (conv.needs_root)
- needs_root_buffer = true;
- localStep.needs_root = conv.needs_root;
- localStep.key = key;
- steps.push(localStep);
- size += conv.size;
+export function get_method_signature_info (typePtr : MonoType, methodPtr : MonoMethod) : MarshalSignatureInfo {
+ if (!methodPtr)
+ throw new Error("Method ptr not provided");
+
+ let result = _method_signature_info_table.get(methodPtr);
+ const classMismatch = !!result && (result.typePtr !== typePtr);
+ if (!result) {
+ const typeName = _get_type_name(typePtr);
+ const json = cswraps.make_marshal_signature_info(typePtr, methodPtr);
+ if (!json)
+ throw new Error(`MakeMarshalSignatureInfo failed for type ${typeName}`);
+
+ result = JSON.parse(json);
+ result.typePtr = typePtr;
+
+ if (classMismatch)
+ console.log("WARNING: Class ptr mismatch for signature info, so caching is disabled");
+ else
+ _method_signature_info_table.set(methodPtr, result);
}
-
- return {
- steps, size, args_marshal,
- is_result_definitely_unmarshaled,
- is_result_possibly_unmarshaled,
- result_unmarshaled_if_argc,
- needs_root_buffer
- };
+ return result;
}
-function _get_converter_for_marshal_string(args_marshal: string/*ArgsMarshalString*/): Converter {
+function _get_converter_for_marshal_string(typePtr: MonoType, method: MonoMethod, args_marshal: string): SignatureConverter | undefined {
let converter = _signature_converters.get(args_marshal);
- if (!converter) {
- converter = _create_converter_for_marshal_string(args_marshal);
- _signature_converters.set(args_marshal, converter);
+ let map : Map | null = null;
+ if (converter instanceof Map) {
+ map = converter;
+ converter = map.get(method);
}
-
return converter;
}
-export function _compile_converter_for_marshal_string(args_marshal: string/*ArgsMarshalString*/): Converter {
- const converter = _get_converter_for_marshal_string(args_marshal);
- if (typeof (converter.args_marshal) !== "string")
- throw new Error("Corrupt converter for '" + args_marshal + "'");
+function _setSpan (offset : VoidPtr, span : Array) : void {
+ if (!Array.isArray(span) || (span.length !== 2))
+ throw new Error(`Span must be an array of shape [offset, length_in_elements] but was ${span}`);
+ setU32(offset, span[0]);
+ setU32(offset + 4, span[1]);
+}
- if (converter.compiled_function && converter.compiled_variadic_function)
- return converter;
+function _bindingsError (message : string) : void {
+ throw new Error(message);
+}
- const converterName = args_marshal.replace("!", "_result_unmarshaled");
- converter.name = converterName;
+function _generate_args_marshaler (typePtr: MonoType, method: MonoMethod, args_marshal: string): string {
+ const argsRoot = mono_wasm_new_root(),
+ resultRoot = mono_wasm_new_root(),
+ exceptionRoot = mono_wasm_new_root();
+ const generatorMethod = get_method("GenerateArgsMarshaler");
+ const buffer = Module._malloc(64);
- let body = [];
- let argumentNames = ["buffer", "rootBuffer", "method"];
+ try {
+ argsRoot.value = js_string_to_mono_string(args_marshal);
+
+ // Manually assemble an arguments buffer
+ // (RuntimeTypeHandle, RuntimeMethodHandle, string)
+ setU32(buffer + 16, typePtr);
+ setU32(buffer + 32, method);
+ setU32(buffer + 0, buffer + 16);
+ setU32(buffer + 4, buffer + 32);
+ setU32(buffer + 8, argsRoot.value);
+
+ // Invoke the managed method
+ resultRoot.value = cwraps.mono_wasm_invoke_method(generatorMethod, MonoObjectNull, buffer, exceptionRoot.get_address());
+ // If it threw an exception, this will yield us a JS Error instance to throw
+ const exc = _convert_exception_for_method_call(resultRoot.value, exceptionRoot.value);
+ if (exc)
+ throw exc;
+ // Otherwise it returned a managed String containing the JS for our new function
+ return conv_string(resultRoot.value);
+ } finally {
+ resultRoot.release();
+ exceptionRoot.release();
+ argsRoot.release();
+ Module._free(buffer);
+ }
+}
- // worst-case allocation size instead of allocating dynamically, plus padding
- const bufferSizeBytes = converter.size + (args_marshal.length * 4) + 16;
+function _generate_bound_method (typePtr: MonoType, method: MonoMethod, args_marshal: string, friendly_name: string): string {
+ const argsRoot = mono_wasm_new_root(),
+ nameRoot = mono_wasm_new_root(),
+ resultRoot = mono_wasm_new_root(),
+ exceptionRoot = mono_wasm_new_root();
+ const generatorMethod = get_method("GenerateBoundMethod");
+ const buffer = Module._malloc(64);
- // ensure the indirect values are 8-byte aligned so that aligned loads and stores will work
- const indirectBaseOffset = ((((args_marshal.length * 4) + 7) / 8) | 0) * 8;
+ try {
+ argsRoot.value = js_string_to_mono_string(args_marshal);
+ nameRoot.value = js_string_to_mono_string(friendly_name);
+
+ // Manually assemble an arguments buffer
+ // (RuntimeTypeHandle, RuntimeMethodHandle, string)
+ setU32(buffer + 16, typePtr);
+ setU32(buffer + 32, method);
+ setU32(buffer + 0, buffer + 16);
+ setU32(buffer + 4, buffer + 32);
+ setU32(buffer + 8, argsRoot.value);
+ setU32(buffer + 12, nameRoot.value);
+
+ // Invoke the managed method
+ resultRoot.value = cwraps.mono_wasm_invoke_method(generatorMethod, MonoObjectNull, buffer, exceptionRoot.get_address());
+ // If it threw an exception, this will yield us a JS Error instance to throw
+ const exc = _convert_exception_for_method_call(resultRoot.value, exceptionRoot.value);
+ if (exc)
+ throw exc;
+ // Otherwise it returned a managed String containing the JS for our new function
+ return conv_string(resultRoot.value);
+ } finally {
+ resultRoot.release();
+ exceptionRoot.release();
+ nameRoot.release();
+ argsRoot.release();
+ Module._free(buffer);
+ }
+}
+function _initialize_bindings_named_closures () : void {
+ // HACK: Populate the lookup table used by compiled closures
const closure: any = {
- Module,
+ _create_temp_frame,
+ _error: _bindingsError,
+ _get_args_root_buffer_for_method_call,
+ _get_buffer_for_method_call,
+ _handle_exception_for_call,
+ _js_to_mono_obj,
+ _js_to_mono_uri,
_malloc: Module._malloc,
+ _pick_automatic_converter_for_type,
+ _setSpan,
+ _teardown_after_call,
+ _unbox_mono_obj_root_with_known_nonprimitive_type,
+ invoke_method: cwraps.mono_wasm_invoke_method,
+ js_string_to_mono_string_interned,
+ js_string_to_mono_string,
+ js_to_mono_enum,
+ mono_wasm_new_root,
+ mono_wasm_try_unbox_primitive_and_get_type: cwraps.mono_wasm_try_unbox_primitive_and_get_type,
mono_wasm_unbox_rooted: cwraps.mono_wasm_unbox_rooted,
- setI32,
- setU32,
+ getF32,
+ getF64,
+ getI32,
+ getU32,
setF32,
setF64,
- setI64
- };
- let indirectLocalOffset = 0;
-
- body.push(
- "if (!method) throw new Error('no method provided');",
- `if (!buffer) buffer = _malloc (${bufferSizeBytes});`,
- `let indirectStart = buffer + ${indirectBaseOffset};`,
- ""
- );
-
- for (let i = 0; i < converter.steps.length; i++) {
- const step = converter.steps[i];
- const closureKey = "step" + i;
- const valueKey = "value" + i;
-
- const argKey = "arg" + i;
- argumentNames.push(argKey);
-
- if (step.convert) {
- closure[closureKey] = step.convert;
- body.push(`let ${valueKey} = ${closureKey}(${argKey}, method, ${i});`);
- } else {
- body.push(`let ${valueKey} = ${argKey};`);
- }
-
- if (step.needs_root) {
- body.push("if (!rootBuffer) throw new Error('no root buffer provided');");
- body.push(`rootBuffer.set (${i}, ${valueKey});`);
- }
-
- // HACK: needs_unbox indicates that we were passed a pointer to a managed object, and either
- // it was already rooted by our caller or (needs_root = true) by us. Now we can unbox it and
- // pass the raw address of its boxed value into the callee.
- // FIXME: I don't think this is GC safe
- if (step.needs_unbox)
- body.push(`${valueKey} = mono_wasm_unbox_rooted (${valueKey});`);
-
- if (step.indirect) {
- const offsetText = `(indirectStart + ${indirectLocalOffset})`;
-
- switch (step.indirect) {
- case "u32":
- body.push(`setU32(${offsetText}, ${valueKey});`);
- break;
- case "i32":
- body.push(`setI32(${offsetText}, ${valueKey});`);
- break;
- case "float":
- body.push(`setF32(${offsetText}, ${valueKey});`);
- break;
- case "double":
- body.push(`setF64(${offsetText}, ${valueKey});`);
- break;
- case "i64":
- body.push(`setI64(${offsetText}, ${valueKey});`);
- break;
- default:
- throw new Error("Unimplemented indirect type: " + step.indirect);
- }
-
- body.push(`setU32(buffer + (${i} * 4), ${offsetText});`);
- indirectLocalOffset += step.size!;
- } else {
- body.push(`setI32(buffer + (${i} * 4), ${valueKey});`);
- indirectLocalOffset += 4;
- }
- body.push("");
- }
-
- body.push("return buffer;");
-
- let bodyJs = body.join("\r\n"), compiledFunction = null, compiledVariadicFunction = null;
- try {
- compiledFunction = _create_named_function("converter_" + converterName, argumentNames, bodyJs, closure);
- converter.compiled_function = compiledFunction;
- } catch (exc) {
- converter.compiled_function = null;
- console.warn("compiling converter failed for", bodyJs, "with error", exc);
- throw exc;
- }
-
-
- argumentNames = ["existingBuffer", "rootBuffer", "method", "args"];
- const variadicClosure = {
- converter: compiledFunction
+ setI32,
+ setI64,
+ setU32,
};
- body = [
- "return converter(",
- " existingBuffer, rootBuffer, method,"
- ];
-
- for (let i = 0; i < converter.steps.length; i++) {
- body.push(
- " args[" + i +
- (
- (i == converter.steps.length - 1)
- ? "]"
- : "], "
- )
- );
- }
-
- body.push(");");
-
- bodyJs = body.join("\r\n");
- try {
- compiledVariadicFunction = _create_named_function("variadic_converter_" + converterName, argumentNames, bodyJs, variadicClosure);
- converter.compiled_variadic_function = compiledVariadicFunction;
- } catch (exc) {
- converter.compiled_variadic_function = null;
- console.warn("compiling converter failed for", bodyJs, "with error", exc);
- throw exc;
- }
-
- converter.scratchRootBuffer = null;
- converter.scratchBuffer = VoidPtrNull;
-
- return converter;
+ for (const k in closure)
+ bindings_named_closures.set(k, closure[k]);
}
-function _maybe_produce_signature_warning(converter: Converter) {
- if (converter.has_warned_about_signature)
- return;
+function _get_api (key: string): Function {
+ if (!bindings_named_closures_initialized) {
+ bindings_named_closures_initialized = true;
+ _initialize_bindings_named_closures();
+ }
- console.warn("MONO_WASM: Deprecated raw return value signature: '" + converter.args_marshal + "'. End the signature with '!' instead of 'm'.");
- converter.has_warned_about_signature = true;
+ const result = bindings_named_closures.get(key);
+ if (!result || typeof(result) !== "function")
+ throw new Error(`Expected ${key} to be a function but was '${result}'`);
+ return result;
}
-export function _decide_if_result_is_marshaled(converter: Converter, argc: number): boolean {
- if (!converter)
- return true;
+export function _compile_converter_for_marshal_string(typePtr: MonoType, method: MonoMethod, args_marshal: string): SignatureConverter {
+ const converter = _get_converter_for_marshal_string(typePtr, method, args_marshal);
+ if (converter && converter.compiled_function && converter.compiled_variadic_function)
+ return converter;
- if (
- converter.is_result_possibly_unmarshaled &&
- (argc === converter.result_unmarshaled_if_argc)
- ) {
- if (argc < converter.result_unmarshaled_if_argc)
- throw new Error(`Expected >= ${converter.result_unmarshaled_if_argc} argument(s) but got ${argc} for signature '${converter.args_marshal}'`);
+ let csFuncResult : any = null;
+ // HACK: We invoke this method directly instead of using the cswraps. version, since that wrapper relies on this function
+ const js = _generate_args_marshaler(typePtr, method, args_marshal);
+ const csFunc = new Function("get_api", "get_type_converter", js);
+ csFuncResult = csFunc(_get_api, _pick_automatic_converter_for_type);
+
+ if (csFuncResult.contains_auto) {
+ let map =