Skip to content

Commit

Permalink
[wasm] optimize interop startup and linker hints (#77256)
Browse files Browse the repository at this point in the history
Prepare for making legacy interop optional and small optimization
  • Loading branch information
pavelsavara authored Dec 9, 2022
1 parent 7cecd48 commit add75d0
Show file tree
Hide file tree
Showing 13 changed files with 250 additions and 238 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
<Compile Include="System\Runtime\InteropServices\JavaScript\Legacy\DataView.cs" />
<Compile Include="System\Runtime\InteropServices\JavaScript\Legacy\Function.cs" />
<Compile Include="System\Runtime\InteropServices\JavaScript\Legacy\Uint8Array.cs" />
<Compile Include="System\Runtime\InteropServices\JavaScript\Legacy\LegacyHostImplementation.cs" />

<Compile Include="System\Runtime\InteropServices\JavaScript\JSHost.cs" />
<Compile Include="System\Runtime\InteropServices\JavaScript\JSMarshalerType.cs" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
namespace System.Runtime.InteropServices.JavaScript
{
// this maps to src\mono\wasm\runtime\corebindings.ts
// the methods are protected from trimming by DynamicDependency on JSFunctionBinding.BindJSFunction
// the public methods are protected from trimming by DynamicDependency on JSFunctionBinding.BindJSFunction
internal static unsafe partial class JavaScriptExports
{
[MethodImpl(MethodImplOptions.NoInlining)] // https://github.com/dotnet/runtime/issues/71425
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
namespace System.Runtime.InteropServices.JavaScript
{
// this maps to src\mono\wasm\runtime\legacy\corebindings.ts
// the methods are protected from trimming by DynamicDependency on JSFunctionBinding.BindJSFunction
// the public methods are protected from trimming by DynamicDependency on JSFunctionBinding.BindJSFunction
internal static unsafe partial class LegacyExports
{
[MethodImplAttribute(MethodImplOptions.NoInlining)] // https://github.com/dotnet/runtime/issues/71425
Expand Down Expand Up @@ -56,7 +56,7 @@ public static IntPtr TryGetCSOwnedObjectJSHandleRef(in object rawObj, int should
}

[MethodImplAttribute(MethodImplOptions.NoInlining)] // https://github.com/dotnet/runtime/issues/71425
public static void CreateCSOwnedProxyRef(nint jsHandle, JSHostImplementation.MappedType mappedType, int shouldAddInflight, out JSObject jsObject)
public static void CreateCSOwnedProxyRef(nint jsHandle, LegacyHostImplementation.MappedType mappedType, int shouldAddInflight, out JSObject jsObject)
{
JSObject? res = null;

Expand All @@ -69,12 +69,12 @@ public static void CreateCSOwnedProxyRef(nint jsHandle, JSHostImplementation.Map
#pragma warning disable CS0612 // Type or member is obsolete
res = mappedType switch
{
JSHostImplementation.MappedType.JSObject => new JSObject(jsHandle),
JSHostImplementation.MappedType.Array => new Array(jsHandle),
JSHostImplementation.MappedType.ArrayBuffer => new ArrayBuffer(jsHandle),
JSHostImplementation.MappedType.DataView => new DataView(jsHandle),
JSHostImplementation.MappedType.Function => new Function(jsHandle),
JSHostImplementation.MappedType.Uint8Array => new Uint8Array(jsHandle),
LegacyHostImplementation.MappedType.JSObject => new JSObject(jsHandle),
LegacyHostImplementation.MappedType.Array => new Array(jsHandle),
LegacyHostImplementation.MappedType.ArrayBuffer => new ArrayBuffer(jsHandle),
LegacyHostImplementation.MappedType.DataView => new DataView(jsHandle),
LegacyHostImplementation.MappedType.Function => new Function(jsHandle),
LegacyHostImplementation.MappedType.Uint8Array => new Uint8Array(jsHandle),
_ => throw new ArgumentOutOfRangeException(nameof(mappedType))
};
#pragma warning restore CS0612 // Type or member is obsolete
Expand Down Expand Up @@ -265,8 +265,8 @@ public static string GetCallSignatureRef(IntPtr _methodHandle, in object objForR
for (int i = 0; i < parmsLength; i++)
{
Type t = parms[i].ParameterType;
var mt = JSHostImplementation.GetMarshalTypeFromType(t);
result[i] = JSHostImplementation.GetCallSignatureCharacterForMarshalType(mt, null);
var mt = LegacyHostImplementation.GetMarshalTypeFromType(t);
result[i] = LegacyHostImplementation.GetCallSignatureCharacterForMarshalType(mt, null);
}

return new string(result);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,61 +24,5 @@ public struct IntPtrAndHandle
[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
}

// please keep BINDING wasm_type_symbol in sync
public enum MappedType
{
JSObject = 0,
Array = 1,
ArrayBuffer = 2,
DataView = 3,
Function = 4,
Uint8Array = 11,
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,21 +13,12 @@ namespace System.Runtime.InteropServices.JavaScript
internal static partial class JSHostImplementation
{
private const string TaskGetResultName = "get_Result";
private static readonly MethodInfo s_taskGetResultMethodInfo = typeof(Task<>).GetMethod(TaskGetResultName)!;
private static MethodInfo? s_taskGetResultMethodInfo;
// we use this to maintain identity of JSHandle for a JSObject proxy
public static readonly Dictionary<int, WeakReference<JSObject>> s_csOwnedObjects = new Dictionary<int, WeakReference<JSObject>>();
// we use this to maintain identity of GCHandle for a managed object
public static Dictionary<object, IntPtr> s_gcHandleFromJSOwnedObject = new Dictionary<object, IntPtr>(ReferenceEqualityComparer.Instance);

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void RegisterCSOwnedObject(JSObject proxy)
{
lock (s_csOwnedObjects)
{
s_csOwnedObjects[(int)proxy.JSHandle] = new WeakReference<JSObject>(proxy, trackResurrection: true);
}
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void ReleaseCSOwnedObject(nint jsHandle)
{
Expand All @@ -41,8 +32,7 @@ public static void ReleaseCSOwnedObject(nint jsHandle)
}
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static object? GetTaskResult(Task task)
public static object? GetTaskResultDynamic(Task task)
{
MethodInfo method = GetTaskResultMethodInfo(task.GetType());
if (method != null)
Expand Down Expand Up @@ -91,145 +81,6 @@ public static RuntimeMethodHandle GetMethodHandleFromIntPtr(IntPtr ptr)
return temp.methodHandle;
}

public 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 JSException($"Unsupported enum underlying type {typeCode}");
}
}

switch (typeCode)
{
case TypeCode.SByte:
case TypeCode.Int16:
case TypeCode.Int32:
return MarshalType.INT;
case TypeCode.Byte:
case TypeCode.UInt16:
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 JSException("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:
return MarshalType.ARRAY_UINT;
case TypeCode.Single:
return MarshalType.ARRAY_FLOAT;
case TypeCode.Double:
return MarshalType.ARRAY_DOUBLE;
default:
throw new JSException($"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;
else if (type.FullName == "System.Uri")
return MarshalType.URI;
else if (type.IsPointer)
return MarshalType.POINTER;

if (type.IsValueType)
return MarshalType.VT;
else
return MarshalType.OBJECT;
}

public static char GetCallSignatureCharacterForMarshalType(MarshalType t, char? defaultValue)
{
switch (t)
{
case MarshalType.BOOL:
return 'b';
case MarshalType.UINT32:
case MarshalType.POINTER:
return 'I';
case MarshalType.INT:
return 'i';
case MarshalType.UINT64:
return 'L';
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'; // this is wrong for uint enums
case MarshalType.ENUM64:
return 'k'; // this is wrong for ulong enums
case MarshalType.TASK:
case MarshalType.DELEGATE:
case MarshalType.OBJECT:
return 'o';
case MarshalType.VT:
return 'a';
default:
if (defaultValue.HasValue)
return defaultValue.Value;
else
throw new JSException($"Unsupported marshal type {t}");
}
}

/// <summary>
/// Gets the MethodInfo for the Task{T}.Result property getter.
/// </summary>
Expand All @@ -242,15 +93,19 @@ public static char GetCallSignatureCharacterForMarshalType(MarshalType t, char?
/// ensuring that trimming doesn't change the application's behavior.
/// </remarks>
[UnconditionalSuppressMessage("ReflectionAnalysis", "IL2070:UnrecognizedReflectionPattern",
Justification = "Task<T>.Result is preserved by the ILLinker because _taskGetResultMethodInfo was initialized with it.")]
Justification = "Task<T>.Result is preserved by the ILLinker because s_taskGetResultMethodInfo was initialized with it.")]
public static MethodInfo GetTaskResultMethodInfo(Type taskType)
{
if (taskType != null)
{
MethodInfo? result = taskType.GetMethod(TaskGetResultName);
if (result != null && result.HasSameMetadataDefinitionAs(s_taskGetResultMethodInfo))
if (s_taskGetResultMethodInfo == null)
{
s_taskGetResultMethodInfo = typeof(Task<>).GetMethod(TaskGetResultName);
}
MethodInfo? getter = taskType.GetMethod(TaskGetResultName);
if (getter != null && getter.HasSameMetadataDefinitionAs(s_taskGetResultMethodInfo!))
{
return result;
return getter;
}
}

Expand All @@ -269,7 +124,7 @@ public static void ThrowException(ref JSMarshalerArgument arg)
throw new InvalidProgramException();
}

public static async Task<JSObject> ImportAsync(string moduleName, string moduleUrl, CancellationToken cancellationToken )
public static async Task<JSObject> ImportAsync(string moduleName, string moduleUrl, CancellationToken cancellationToken)
{
Task<JSObject> modulePromise = JavaScriptImports.DynamicImport(moduleName, moduleUrl);
var wrappedTask = CancelationHelper(modulePromise, cancellationToken);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ public class Array : JSObject
public Array(params object[] _params)
: base(JavaScriptImports.CreateCSOwnedObject(nameof(Array), _params))
{
JSHostImplementation.RegisterCSOwnedObject(this);
LegacyHostImplementation.RegisterCSOwnedObject(this);
}

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ public class ArrayBuffer : JSObject
public ArrayBuffer(int length)
: base(JavaScriptImports.CreateCSOwnedObject(nameof(ArrayBuffer), new object[] { length }))
{
JSHostImplementation.RegisterCSOwnedObject(this);
LegacyHostImplementation.RegisterCSOwnedObject(this);
}

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ public class DataView : JSObject
public DataView(ArrayBuffer buffer)
: base(JavaScriptImports.CreateCSOwnedObject(nameof(DataView), new object[] { buffer }))
{
JSHostImplementation.RegisterCSOwnedObject(this);
LegacyHostImplementation.RegisterCSOwnedObject(this);
}

/// <summary>
Expand All @@ -28,7 +28,7 @@ public DataView(ArrayBuffer buffer)
public DataView(ArrayBuffer buffer, int byteOffset)
: base(JavaScriptImports.CreateCSOwnedObject(nameof(DataView), new object[] { buffer, byteOffset }))
{
JSHostImplementation.RegisterCSOwnedObject(this);
LegacyHostImplementation.RegisterCSOwnedObject(this);
}

/// <summary>
Expand All @@ -40,7 +40,7 @@ public DataView(ArrayBuffer buffer, int byteOffset)
public DataView(ArrayBuffer buffer, int byteOffset, int byteLength)
: base(JavaScriptImports.CreateCSOwnedObject(nameof(DataView), new object[] { buffer, byteOffset, byteLength }))
{
JSHostImplementation.RegisterCSOwnedObject(this);
LegacyHostImplementation.RegisterCSOwnedObject(this);
}

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ public class Function : JSObject
public Function(params object[] args)
: base(JavaScriptImports.CreateCSOwnedObject(nameof(Function), args))
{
JSHostImplementation.RegisterCSOwnedObject(this);
LegacyHostImplementation.RegisterCSOwnedObject(this);
}

internal Function(IntPtr jsHandle) : base(jsHandle)
Expand Down
Loading

0 comments on commit add75d0

Please sign in to comment.