Skip to content

Commit

Permalink
Update Emit code for new Nullability; still running non-emit for tests
Browse files Browse the repository at this point in the history
  • Loading branch information
steveharter committed Apr 20, 2022
1 parent 19959c0 commit 40eb391
Show file tree
Hide file tree
Showing 4 changed files with 97 additions and 137 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -13,16 +13,11 @@ internal static class InvokerEmitUtil
// This is an instance method to avoid overhead of shuffle thunk.
internal unsafe delegate object? InvokeFunc<T>(T obj, object? target, IntPtr* arguments);

// Avoid high arg count due to increased stack.
// This also allows the local variables to use that more smaller encoded _S opcode variants.
public const int MaxArgumentCount = 64;

public static unsafe InvokeFunc<T> CreateInvokeDelegate<T>(MethodBase method)
{
Debug.Assert(!method.ContainsGenericParameters);

ParameterInfo[] parameters = method.GetParametersNoCopy();
Debug.Assert(parameters.Length <= MaxArgumentCount);

Type[] delegateParameters = new Type[3] { typeof(T), typeof(object), typeof(IntPtr*) };

Expand All @@ -44,14 +39,8 @@ public static unsafe InvokeFunc<T> CreateInvokeDelegate<T>(MethodBase method)
else
{
HandleThisPointer(il, method);
PushArguments(il, parameters, out NullableRefInfo[]? byRefNullables, out int byRefNullableCount);
PushArguments(il, parameters);
Invoke(il, method);

if (byRefNullableCount > 0)
{
Debug.Assert(byRefNullables != null); ;
UpdateNullables(il, parameters, byRefNullables, byRefNullableCount);
}
}

HandleReturn(il, method);
Expand All @@ -77,144 +66,33 @@ private static void HandleThisPointer(ILGenerator il, MethodBase method)
/// <summary>
/// Push each argument.
/// </summary>
private static void PushArguments(
ILGenerator il,
ParameterInfo[] parameters,
out NullableRefInfo[]? byRefNullables,
out int byRefNullablesCount
)
private static void PushArguments(ILGenerator il, ParameterInfo[] parameters)
{
byRefNullables = null;
byRefNullablesCount = 0;

LocalBuilder? local_pArg = il.DeclareLocal(typeof(IntPtr*));
il.Emit(OpCodes.Ldarg_2);
il.Emit(OpCodes.Stloc_S, local_pArg);

for (int i = 0; i < parameters.Length; i++)
{
RuntimeType parameterType = (RuntimeType)parameters[i].ParameterType;

// Get the argument as a ref
il.Emit(OpCodes.Ldloc_S, local_pArg);
il.Emit(OpCodes.Ldind_Ref);

if (parameterType.IsByRef)
if (parameterType.IsPointer)
{
RuntimeType elementType = (RuntimeType)parameterType.GetElementType();

if (elementType.IsNullableOfT)
{
byRefNullables ??= new NullableRefInfo[MaxArgumentCount - i];

LocalBuilder tmp = il.DeclareLocal(typeof(object).MakeByRefType());
il.Emit(OpCodes.Stloc_S, tmp);
il.Emit(OpCodes.Ldloc_S, tmp);

// Get the raw pointer.
il.Emit(OpCodes.Ldobj, typeof(object));
il.Emit(OpCodes.Unbox, elementType);

// Copy the pointer to the temp variable and load as a ref.
LocalBuilder byRefPtr = il.DeclareLocal(elementType.MakePointerType());
il.Emit(OpCodes.Stloc_S, byRefPtr);
il.Emit(OpCodes.Ldloca_S, byRefPtr);
il.Emit(OpCodes.Ldind_Ref);

byRefNullables[byRefNullablesCount++] = new NullableRefInfo { ParameterIndex = i, LocalIndex = byRefPtr.LocalIndex };
}
else if (elementType.IsPointer)
{
LocalBuilder tmp = il.DeclareLocal(typeof(IntPtr).MakeByRefType());
il.Emit(OpCodes.Stloc_S, tmp);
il.Emit(OpCodes.Ldloc_S, tmp);
il.Emit(OpCodes.Ldobj, typeof(IntPtr).MakeByRefType());
}
else
{
LocalBuilder tmp = il.DeclareLocal(parameterType);
il.Emit(OpCodes.Stloc_S, tmp);
il.Emit(OpCodes.Ldloca_S, tmp);
il.Emit(OpCodes.Ldind_Ref); //keep this or remove and use ldloca?
}
parameterType = (RuntimeType)typeof(IntPtr);
}
else if (parameterType.IsNullableOfT)
{
// Nullable<T> is the only incoming value type that is boxed.
LocalBuilder tmp = il.DeclareLocal(typeof(object).MakeByRefType());
il.Emit(OpCodes.Stloc_S, tmp);
il.Emit(OpCodes.Ldloc_S, tmp);

il.Emit(OpCodes.Ldobj, typeof(object));
il.Emit(OpCodes.Unbox, parameterType);
il.Emit(OpCodes.Ldobj, parameterType);
}
else if (parameterType.IsPointer)
{
LocalBuilder tmp = il.DeclareLocal(typeof(IntPtr).MakeByRefType());
il.Emit(OpCodes.Stloc_S, tmp);
il.Emit(OpCodes.Ldloc_S, tmp);
il.Emit(OpCodes.Ldobj, typeof(IntPtr));
}
else
il.Emit(OpCodes.Ldarg_2);
if (i != 0)
{
LocalBuilder tmp = il.DeclareLocal(parameterType.MakeByRefType());
il.Emit(OpCodes.Stloc_S, tmp);
il.Emit(OpCodes.Ldloc_S, tmp);
il.Emit(OpCodes.Ldobj, parameterType);
il.Emit(OpCodes.Ldc_I4, i * IntPtr.Size);
il.Emit(OpCodes.Add);
}

// Move to the next argument.
if (i < parameters.Length - 1)
il.Emit(OpCodes.Call, Methods.ByReferenceOfByte_Value()); // This can be replaced by ldfld once byref fields are available in C#
if (!parameterType.IsByRef)
{
il.Emit(OpCodes.Ldloc_S, local_pArg);
il.Emit(OpCodes.Sizeof, typeof(IntPtr));
il.Emit(OpCodes.Add);
il.Emit(OpCodes.Stloc_S, local_pArg);
il.Emit(OpCodes.Ldobj, parameterType);
}
}
}

/// <summary>
/// Update any nullables that were passed by reference.
/// </summary>
private static void UpdateNullables(
ILGenerator il,
ParameterInfo[] parameters,
NullableRefInfo[] byRefNullables,
int byRefNullablesCount)
{
for (int i = 0; i < byRefNullablesCount; i++)
{
NullableRefInfo info = byRefNullables[i];

RuntimeType parameterType = (RuntimeType)parameters[info.ParameterIndex].ParameterType;
Debug.Assert(parameterType.IsByRef);

RuntimeType? elementType = (RuntimeType)parameterType.GetElementType()!;
Debug.Assert(elementType.IsNullableOfT);

// Get the original byref location.
il.Emit(OpCodes.Ldc_I4_S, info.ParameterIndex);
il.Emit(OpCodes.Conv_I);
il.Emit(OpCodes.Sizeof, typeof(IntPtr));
il.Emit(OpCodes.Mul);
il.Emit(OpCodes.Ldarg_2);
il.Emit(OpCodes.Add);
il.Emit(OpCodes.Ldind_I);

// Get the Nullable<T>& value and update the original.
il.Emit(OpCodes.Ldloc_S, info.LocalIndex);
il.Emit(OpCodes.Ldobj, elementType);
il.Emit(OpCodes.Box, elementType);
il.Emit(OpCodes.Stind_Ref);
}
}

private static void Invoke(ILGenerator il, MethodBase method)
{
// todo: once we return the value without boxing add support for OpCodes.Tailcall for perf.

bool emitNew = method is RuntimeConstructorInfo;
if (emitNew)
{
Expand Down Expand Up @@ -310,6 +188,12 @@ private struct NullableRefInfo

private static class Methods
{
private static MethodInfo? s_ByReferenceOfByte_Value;
[DynamicDependency(DynamicallyAccessedMemberTypes.PublicMethods, typeof(ByReference<>))]
public static MethodInfo ByReferenceOfByte_Value() =>
s_ByReferenceOfByte_Value ??
(s_ByReferenceOfByte_Value = typeof(ByReference<byte>).GetMethod("get_Value")!);

private static MethodInfo? s_ThrowHelper_Throw_NullReference_InvokeNullRefReturned;
[DynamicDependency(DynamicallyAccessedMemberTypes.PublicMethods, typeof(ThrowHelper))]
public static MethodInfo ThrowHelper_Throw_NullReference_InvokeNullRefReturned() =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,12 +35,10 @@ public MethodInvoker(RuntimeMethodInfo methodInfo)
if (!_strategyDetermined)
{
if (RuntimeFeature.IsDynamicCodeCompiled &&
_methodInfo.GetParametersNoCopy().Length <= InvokerEmitUtil.MaxArgumentCount &&
_methodInfo.DeclaringType != typeof(Type) // Avoid stack crawl issue with GetType().
)
_methodInfo.DeclaringType != typeof(Type)) // Avoid stack crawl issue with GetType().
{
// For testing slow path, disable Emit for now
//_emitInvoke = InvokerEmitUtil.CreateInvokeDelegate<MethodInvoker>(_methodInfo);
// _emitInvoke = InvokerEmitUtil.CreateInvokeDelegate<MethodInvoker>(_methodInfo);
}

_strategyDetermined = true;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,27 @@ public void Method(byte* ptr, int expected)
Assert.Equal(expected, unchecked((int)ptr));
}

public void MethodWithSystemPointer(Pointer ptr, int expected)
{
Assert.Equal(expected, unchecked((int)Pointer.Unbox(ptr)));
}

public bool* Return(int expected)
{
return unchecked((bool*)expected);
}

public object ReturnWithSystemPointer(int expected)
{
return Pointer.Box((byte*)expected, typeof(byte*));
}
}

unsafe delegate void MethodDelegate(byte* ptr, int expected);
unsafe delegate void MethodDelegateWithSystemPointer(Pointer ptr, int expected);

unsafe delegate bool* ReturnDelegate(int expected);
unsafe delegate object ReturnDelegateWithSystemPointer(int expected);

public unsafe class PointerTests
{
Expand Down Expand Up @@ -242,6 +254,15 @@ public void PointerMethodDelegateParameter(int value)
d.DynamicInvoke(Pointer.Box(unchecked((void*)value), typeof(byte*)), value);
}

[Theory]
[MemberData(nameof(Pointers))]
public void MethodDelegateParameter_SystemPointer(int value)
{
var obj = new PointerHolder();
MethodDelegateWithSystemPointer d = obj.MethodWithSystemPointer;
d.DynamicInvoke(Pointer.Box(unchecked((void*)value), typeof(byte*)), value);
}

[Fact]
public void PointerNullMethodDelegateParameter()
{
Expand All @@ -250,6 +271,24 @@ public void PointerNullMethodDelegateParameter()
d.DynamicInvoke(null, 0);
}

[Fact]
public void PointerNullMethodDelegateParameter_InvalidType_SystemPointer()
{
// An null is not converted to a System.Pointer.
var obj = new PointerHolder();
MethodDelegateWithSystemPointer d = obj.MethodWithSystemPointer;
try
{
d.DynamicInvoke(null, 0);
}
catch (TargetInvocationException e) when (e.InnerException is ArgumentException)
{
return;
}

Assert.Fail("Inner exception should be ArgumentException.");
}

[Theory]
[MemberData(nameof(Pointers))]
public void IntPtrMethodDelegateParameter(int value)
Expand All @@ -259,6 +298,19 @@ public void IntPtrMethodDelegateParameter(int value)
d.DynamicInvoke((IntPtr)value, value);
}

[Theory]
[MemberData(nameof(Pointers))]
public void IntPtrMethodDelegateParameter_InvalidType_SystemPointer(int value)
{
// An IntPtr is not converted to a System.Pointer.
var obj = new PointerHolder();
MethodDelegateWithSystemPointer d = obj.MethodWithSystemPointer;
AssertExtensions.Throws<ArgumentException>(null, () =>
{
d.DynamicInvoke((IntPtr)value, value);
});
}

[Theory]
[MemberData(nameof(Pointers))]
public void PointerMethodDelegateParameter_InvalidType(int value)
Expand All @@ -271,6 +323,16 @@ public void PointerMethodDelegateParameter_InvalidType(int value)
});
}

[Theory]
[MemberData(nameof(Pointers))]
public void PointerMethodDelegateParameter_InvalidType_SystemPointer(int value)
{
// Although the type boxed doesn't match, when unboxing void* is returned.
var obj = new PointerHolder();
MethodDelegateWithSystemPointer d = obj.MethodWithSystemPointer;
d.DynamicInvoke(Pointer.Box(unchecked((void*)value), typeof(long*)), value);
}

[Theory]
[MemberData(nameof(Pointers))]
public void PointerMethodDelegateReturn(int value)
Expand All @@ -282,5 +344,17 @@ public void PointerMethodDelegateReturn(int value)
void* actualPointer = Pointer.Unbox(actualValue);
Assert.Equal(value, unchecked((int)actualPointer));
}

[Theory]
[MemberData(nameof(Pointers))]
public void PointerMethodDelegateReturn_SystemPointer(int value)
{
var obj = new PointerHolder();
ReturnDelegateWithSystemPointer d = obj.ReturnWithSystemPointer;
object actualValue = d.DynamicInvoke(value);
Assert.IsType<Pointer>(actualValue);
void* actualPointer = Pointer.Unbox(actualValue);
Assert.Equal(value, unchecked((int)actualPointer));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -85,5 +85,9 @@ internal bool IsNullHandle()
{
return value == IntPtr.Zero;
}


// Temporary placeholder until Mono adds support for supporting boxing true Nullables.
internal static object? ReboxFromNullable(object? src) => src;
}
}

0 comments on commit 40eb391

Please sign in to comment.