diff --git a/eng/testing/tests.wasm.targets b/eng/testing/tests.wasm.targets
index 9f8b2eab5ec124..84f7416ea3d697 100644
--- a/eng/testing/tests.wasm.targets
+++ b/eng/testing/tests.wasm.targets
@@ -150,6 +150,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..0fd2653b0a3087 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,9 @@
+
+
+
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..7695fa4d0b3598
--- /dev/null
+++ b/src/libraries/System.Private.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Codegen.cs
@@ -0,0 +1,523 @@
+// 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;
+
+ 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 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",
+ };
+ }
+ }
+
+ 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());
+ }
+ }
+
+ 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 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))" },
+ };
+
+ 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 thisRoot = token.thisArgRoot;");
+ 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( " 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, thisRoot ? thisRoot.value : 0, buffer, exceptionRoot.get_address());");
+ output.AppendLine(" _handle_exception_for_call(converter, token, buffer, resultRoot, exceptionRoot, argsRootBuffer);");
+ 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 {");
+ 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/JSException.cs b/src/libraries/System.Private.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/JSException.cs
index 7fa68522f9d8d5..7d30cb99c6edfa 100644
--- a/src/libraries/System.Private.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/JSException.cs
+++ b/src/libraries/System.Private.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/JSException.cs
@@ -10,6 +10,12 @@ namespace System.Runtime.InteropServices.JavaScript
///
public class JSException : Exception
{
+ public string? Stack;
+
public JSException(string msg) : base(msg) { }
+
+ public JSException(string msg, string? stack) : base(msg) {
+ Stack = stack;
+ }
}
}
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/JSObject.cs b/src/libraries/System.Private.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/JSObject.cs
index 19cb346ad9bb45..9b3b4d05caa31d 100644
--- a/src/libraries/System.Private.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/JSObject.cs
+++ b/src/libraries/System.Private.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/JSObject.cs
@@ -1,6 +1,11 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
+using System.Diagnostics.CodeAnalysis;
+using Console = System.Diagnostics.Debug;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
namespace System.Runtime.InteropServices.JavaScript
{
public interface IJSObject
@@ -15,6 +20,30 @@ public interface IJSObject
///
public partial class JSObject : IJSObject, IDisposable
{
+ [StructLayout(LayoutKind.Sequential)]
+ private struct InvokeRecord {
+ public object?[] Arguments;
+ // FIXME: Make this object? and update Invoke
+ public object Result;
+ public string? ErrorMessage;
+ public string? ErrorStack;
+ }
+
+ [StructLayout(LayoutKind.Sequential)]
+ private struct GetPropertyRecord {
+ public object Value;
+ public string? ErrorMessage;
+ public string? ErrorStack;
+ }
+
+ [StructLayout(LayoutKind.Sequential)]
+ private struct SetPropertyRecord {
+ public object? Value;
+ public string? ErrorMessage;
+ public string? ErrorStack;
+ public int CreateIfNotExists;
+ }
+
///
/// Invoke a named method of the object, or throws a JSException on error.
///
@@ -34,15 +63,23 @@ public partial class JSObject : IJSObject, IDisposable
/// valuews.
///
///
- public object Invoke(string method, params object?[] args)
+ // FIXME: This should be object?, but if we correct it lots of stuff breaks
+ public unsafe object Invoke(string method, params object?[] args)
{
- AssertNotDisposed();
-
- object res = Interop.Runtime.InvokeJSWithArgs(JSHandle, method, args, out int exception);
- if (exception != 0)
- throw new JSException((string)res);
- Interop.Runtime.ReleaseInFlight(res);
- return res;
+ var record = new InvokeRecord {
+ Arguments = args
+ };
+ var pRecord = (IntPtr)Unsafe.AsPointer(ref record);
+ var invokeResult = Runtime.InvokeJSFunctionByName("INTERNAL._JSObject_Invoke", (IntPtr)JSHandle, method, pRecord);
+ if (invokeResult != InvokeJSResult.Success)
+ throw new JSException($"Invoke result was {invokeResult}");
+ else if (record.ErrorMessage != null)
+ throw new JSException(record.ErrorMessage, record.ErrorStack);
+ else {
+ var result = record.Result;
+ Interop.Runtime.ReleaseInFlight(result);
+ return result;
+ }
}
public struct EventListenerOptions {
@@ -128,15 +165,21 @@ public void RemoveEventListener(string name, int listenerGCHandle, EventListener
/// valuews.
///
///
- public object GetObjectProperty(string name)
+ public unsafe object GetObjectProperty(string name)
{
- AssertNotDisposed();
-
- object propertyValue = Interop.Runtime.GetObjectProperty(JSHandle, name, out int exception);
- if (exception != 0)
- throw new JSException((string)propertyValue);
- Interop.Runtime.ReleaseInFlight(propertyValue);
- return propertyValue;
+ var record = new GetPropertyRecord {
+ };
+ var pRecord = (IntPtr)Unsafe.AsPointer(ref record);
+ var invokeResult = Runtime.InvokeJSFunctionByName("INTERNAL._JSObject_GetProperty", (IntPtr)JSHandle, name, pRecord);
+ if (invokeResult != InvokeJSResult.Success)
+ throw new JSException($"Invoke result was {invokeResult}");
+ else if (record.ErrorMessage != null)
+ throw new JSException(record.ErrorMessage, record.ErrorStack);
+ else {
+ var result = record.Value;
+ Interop.Runtime.ReleaseInFlight(result);
+ return result;
+ }
}
///
@@ -149,14 +192,20 @@ public object GetObjectProperty(string name)
/// array that will be surfaced as a typed ArrayBuffer (byte[], sbyte[], short[], ushort[],
/// float[], double[])
/// Defaults to and creates the property on the javascript object if not found, if set to it will not create the property if it does not exist. If the property exists, the value is updated with the provided value.
- ///
- public void SetObjectProperty(string name, object value, bool createIfNotExists = true, bool hasOwnProperty = false)
+ /// does nothing
+ // FIXME: hasOwnProperty is unused.
+ public unsafe void SetObjectProperty(string name, object value, bool createIfNotExists = true, bool hasOwnProperty = false)
{
- AssertNotDisposed();
-
- object setPropResult = Interop.Runtime.SetObjectProperty(JSHandle, name, value, createIfNotExists, hasOwnProperty, out int exception);
- if (exception != 0)
- throw new JSException($"Error setting {name} on (js-obj js '{JSHandle}')");
+ var record = new SetPropertyRecord {
+ Value = value,
+ CreateIfNotExists = createIfNotExists ? 1 : 0
+ };
+ var pRecord = (IntPtr)Unsafe.AsPointer(ref record);
+ var invokeResult = Runtime.InvokeJSFunctionByName("INTERNAL._JSObject_SetProperty", (IntPtr)JSHandle, name, pRecord);
+ if (invokeResult != InvokeJSResult.Success)
+ throw new JSException($"Invoke result was {invokeResult}");
+ else if (record.ErrorMessage != null)
+ throw new JSException(record.ErrorMessage, record.ErrorStack);
}
///
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..c01fca1282379b 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,12 +1,73 @@
// 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;
+using System.Runtime.CompilerServices;
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 InvokeJSResult : int {
+ Success = 0,
+ InvalidFunctionName,
+ FunctionNotFound,
+ InvalidArgumentCount,
+ InvalidArgumentType,
+ MissingArgumentType,
+ NullArgumentPointer,
+ FunctionHadReturnValue,
+ FunctionThrewException,
+ InternalError,
+ }
+
public static partial class Runtime
{
private const string TaskGetResultName = "get_Result";
@@ -22,6 +83,78 @@ public static string InvokeJS(string str)
return Interop.Runtime.InvokeJS(str);
}
+ ///
+ /// Invoke a JS function with a specified name, passing up to 3 argument(s)
+ /// of a specified type at a specified address.
+ /// NOTE: For reference types, argN must be the address of a reference to the object, not the
+ /// address of the object itself. This ensures that the GC can safely move the object.
+ /// For value types (including pointers, ints, etc) argN is the address of the value.
+ ///
+ public static InvokeJSResult InvokeJSFunctionByName (
+ string internedFunctionName, int argumentCount,
+ Type type1, IntPtr address1,
+ Type type2, IntPtr address2,
+ Type type3, IntPtr address3
+ ) {
+ return (InvokeJSResult)Interop.Runtime.InvokeJSFunction(
+ internedFunctionName, argumentCount,
+ type1?.TypeHandle.Value ?? IntPtr.Zero, address1,
+ type2?.TypeHandle.Value ?? IntPtr.Zero, address2,
+ type3?.TypeHandle.Value ?? IntPtr.Zero, address3
+ );
+ }
+
+ public static InvokeJSResult InvokeJSFunctionByName (string internedFunctionName) {
+ return (InvokeJSResult)Interop.Runtime.InvokeJSFunction(
+ internedFunctionName, 0,
+ IntPtr.Zero, IntPtr.Zero,
+ IntPtr.Zero, IntPtr.Zero,
+ IntPtr.Zero, IntPtr.Zero
+ );
+ }
+
+ public static unsafe InvokeJSResult InvokeJSFunctionByName (string internedFunctionName, ref T1 arg1) {
+ var resultCode = Interop.Runtime.InvokeJSFunction(
+ internedFunctionName, 1,
+ typeof(T1).TypeHandle.Value, (IntPtr)Unsafe.AsPointer(ref arg1),
+ IntPtr.Zero, IntPtr.Zero,
+ IntPtr.Zero, IntPtr.Zero
+ );
+ return (InvokeJSResult)resultCode;
+ }
+
+ public static unsafe InvokeJSResult InvokeJSFunctionByName (string internedFunctionName, ref T1 arg1, ref T2 arg2) {
+ var resultCode = Interop.Runtime.InvokeJSFunction(
+ internedFunctionName, 2,
+ typeof(T1).TypeHandle.Value, (IntPtr)Unsafe.AsPointer(ref arg1),
+ typeof(T2).TypeHandle.Value, (IntPtr)Unsafe.AsPointer(ref arg2),
+ IntPtr.Zero, IntPtr.Zero
+ );
+ return (InvokeJSResult)resultCode;
+ }
+
+ public static unsafe InvokeJSResult InvokeJSFunctionByName (string internedFunctionName, ref T1 arg1, ref T2 arg2, ref T3 arg3) {
+ var resultCode = Interop.Runtime.InvokeJSFunction(
+ internedFunctionName, 3,
+ typeof(T1).TypeHandle.Value, (IntPtr)Unsafe.AsPointer(ref arg1),
+ typeof(T2).TypeHandle.Value, (IntPtr)Unsafe.AsPointer(ref arg2),
+ typeof(T3).TypeHandle.Value, (IntPtr)Unsafe.AsPointer(ref arg3)
+ );
+ return (InvokeJSResult)resultCode;
+ }
+
+ public static InvokeJSResult InvokeJSFunctionByName (string internedFunctionName, T1 arg1) {
+ return InvokeJSFunctionByName(internedFunctionName, ref arg1);
+ }
+
+ public static InvokeJSResult InvokeJSFunctionByName (string internedFunctionName, T1 arg1, T2 arg2) {
+ return InvokeJSFunctionByName(internedFunctionName, ref arg1, ref arg2);
+ }
+
+ public static InvokeJSResult InvokeJSFunctionByName (string internedFunctionName, T1 arg1, T2 arg2, T3 arg3) {
+ return InvokeJSFunctionByName(internedFunctionName, ref arg1, ref arg2, ref arg3);
+ }
+
public static Function? CompileFunction(string snippet)
{
return Interop.Runtime.CompileFunction(snippet);
@@ -49,129 +182,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
- }
-
- // 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 static string GetCallSignature(IntPtr methodHandle, object objForRuntimeType)
- {
- IntPtrAndHandle tmp = default(IntPtrAndHandle);
- tmp.ptr = methodHandle;
+ private static RuntimeMethodHandle GetMethodHandleFromIntPtr (IntPtr ptr) {
+ var temp = new IntPtrAndHandle { ptr = ptr };
+ return temp.methodHandle;
+ }
- MethodBase? mb = objForRuntimeType == null ? MethodBase.GetMethodFromHandle(tmp.handle) : MethodBase.GetMethodFromHandle(tmp.handle, Type.GetTypeHandle(objForRuntimeType));
- if (mb == null)
- return string.Empty;
+ private static RuntimeTypeHandle GetTypeHandleFromIntPtr (IntPtr ptr) {
+ var temp = new IntPtrAndHandle { ptr = ptr };
+ return temp.typeHandle;
+ }
- ParameterInfo[] parms = mb.GetParameters();
- int parmsLength = parms.Length;
- if (parmsLength == 0)
- return string.Empty;
+ private static string MakeMarshalTypeRecord (Type type, MarshalType mtype) {
+ var result = $"{{ \"marshalType\": {(int)mtype}, " +
+ $"\"typePtr\": {type.TypeHandle.Value}, " +
+ $"\"signatureChar\": \"{GetCallSignatureCharacterForMarshalType(mtype, 'a')}\" }}";
+ return result;
+ }
- char[] res = new char[parmsLength];
+ private static MethodBase? MethodFromPointers (IntPtr typePtr, IntPtr methodPtr) {
+ if (methodPtr == IntPtr.Zero)
+ return null;
- for (int c = 0; c < parmsLength; c++)
- {
- Type t = parms[c].ParameterType;
- switch (Type.GetTypeCode(t))
- {
+ var methodHandle = GetMethodHandleFromIntPtr(methodPtr);
+
+ if (typePtr != IntPtr.Zero) {
+ var typeHandle = GetTypeHandleFromIntPtr(typePtr);
+ return MethodBase.GetMethodFromHandle(methodHandle, typeHandle);
+ } else {
+ return MethodBase.GetMethodFromHandle(methodHandle);
+ }
+ }
+
+ 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 +563,9 @@ public static string GetCallSignature(IntPtr methodHandle, object objForRuntimeT
return null;
}
- 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)
+ public static string ObjectToString(object? o)
{
- 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 +637,46 @@ 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 Codegen.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 Codegen.MarshalString(signature, method),
+ FriendlyName = friendlyName,
+ };
+ Codegen.GenerateBoundMethod(state);
+ return state.Output.ToString();
+ }
+ }
+
+ 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..82a4893dec1854
--- /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 d7b18fc65e1505..2814cfa350b6cd 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..f94a18eaa3018f 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
@@ -186,8 +186,8 @@ public static async Task Iterator()
return rangeIterator;
");
- const int count = 500;
- for (int attempt = 0; attempt < 100; attempt++)
+ const int count = 500, attemptCount = 100;
+ for (int attempt = 0; attempt < attemptCount; attempt++)
{
int index = 0;
try
@@ -226,8 +226,14 @@ public static IEnumerable