Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Optimize generic MethodInfo for Func<TResult> #4588

Merged
merged 1 commit into from
Mar 2, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
89 changes: 89 additions & 0 deletions src/Microsoft.ML.Core/Utilities/FuncInstanceMethodInfo1`2.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

#nullable enable

using System;
using System.Linq.Expressions;
using System.Reflection;
using Microsoft.ML.Runtime;

namespace Microsoft.ML.Internal.Utilities
{
Copy link
Contributor

@harishsk harishsk Mar 2, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any idea why the file names of the newly added files in this directory have an backtick-1 or backtick-2 suffix? Is that an accident or is there a specific reason behind this naming? The change looks good and I am approving it but it would be good if the naming can be fixed.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The file names match the names of the types defined in the files. When generic types are compiled, the compiler adds a `{arity} suffix to the type name.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What does the {arity} represent here?


In reply to: 386548523 [](ancestors = 386548523)

Copy link
Member Author

@sharwell sharwell Mar 2, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The number of generic type parameters.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm using the term from here:
https://github.com/dotnet/roslyn/blob/6389e2519f282f7683025761189894e1c894936c/src/Compilers/Core/Portable/Symbols/INamedTypeSymbol.cs#L23-L27

I wanted to link to the language specification but it seems the specification doesn't explicitly give a name to this characteristic of generic types.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the additional info. Please feel free to merge.


In reply to: 386566851 [](ancestors = 386566851)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll run a build locally to make sure nothing changed in the interim and merge if it passes.

/// <summary>
/// Represents the <see cref="MethodInfo"/> for a generic function corresponding to <see cref="Func{TResult}"/>,
/// with the following characteristics:
///
/// <list type="bullet">
/// <item><description>The method is an instance method on an object of type <typeparamref name="TTarget"/>.</description></item>
/// <item><description>One generic type argument.</description></item>
/// <item><description>A return value of <typeparamref name="TResult"/>.</description></item>
/// </list>
/// </summary>
/// <typeparam name="TTarget">The type of the receiver of the instance method.</typeparam>
/// <typeparam name="TResult">The type of the return value of the method.</typeparam>
internal sealed class FuncInstanceMethodInfo1<TTarget, TResult> : FuncMethodInfo1<TResult>
where TTarget : class
{
private static readonly string _targetTypeCheckMessage = $"Should have a target type of '{typeof(TTarget)}'";

public FuncInstanceMethodInfo1(Func<TResult> function)
: this(function.Method)
{
}

private FuncInstanceMethodInfo1(MethodInfo methodInfo)
: base(methodInfo)
{
Contracts.CheckParam(!GenericMethodDefinition.IsStatic, nameof(methodInfo), "Should be an instance method");
Contracts.CheckParam(GenericMethodDefinition.DeclaringType == typeof(TTarget), nameof(methodInfo), _targetTypeCheckMessage);
}

/// <summary>
/// Creates a <see cref="FuncInstanceMethodInfo1{TTarget, TResult}"/> representing the <see cref="MethodInfo"/> for
/// a generic instance method. This helper method allows the instance to be created prior to the creation of any
/// instances of the target type. The following example shows the creation of an instance representing the
/// <see cref="object.GetHashCode"/> method:
///
/// <code>
/// FuncInstanceMethodInfo1&lt;object, int&gt;.Create(obj => obj.GetHashCode)
/// </code>
/// </summary>
/// <param name="expression">The expression which creates the delegate for an instance of the target type.</param>
/// <returns>A <see cref="FuncInstanceMethodInfo1{TTarget, TResult}"/> representing the <see cref="MethodInfo"/>
/// for the generic instance method.</returns>
public static FuncInstanceMethodInfo1<TTarget, TResult> Create(Expression<Func<TTarget, Func<TResult>>> expression)
{
if (!(expression is { Body: UnaryExpression { Operand: MethodCallExpression methodCallExpression } }))
{
throw Contracts.ExceptParam(nameof(expression), "Unexpected expression form");
}

// Verify that we are calling MethodInfo.CreateDelegate(Type, object)
Contracts.CheckParam(methodCallExpression.Method.DeclaringType == typeof(MethodInfo), nameof(expression), "Unexpected expression form");
Contracts.CheckParam(methodCallExpression.Method.Name == nameof(MethodInfo.CreateDelegate), nameof(expression), "Unexpected expression form");
Contracts.CheckParam(methodCallExpression.Method.GetParameters().Length == 2, nameof(expression), "Unexpected expression form");
Contracts.CheckParam(methodCallExpression.Method.GetParameters()[0].ParameterType == typeof(Type), nameof(expression), "Unexpected expression form");
Contracts.CheckParam(methodCallExpression.Method.GetParameters()[1].ParameterType == typeof(object), nameof(expression), "Unexpected expression form");

// Verify that we are creating a delegate of type Func<TRet>
Contracts.CheckParam(methodCallExpression.Arguments.Count == 2, nameof(expression), "Unexpected expression form");
Contracts.CheckParam(methodCallExpression.Arguments[0] is ConstantExpression, nameof(expression), "Unexpected expression form");
Contracts.CheckParam(((ConstantExpression)methodCallExpression.Arguments[0]).Type == typeof(Type), nameof(expression), "Unexpected expression form");
Contracts.CheckParam((Type)((ConstantExpression)methodCallExpression.Arguments[0]).Value == typeof(Func<TResult>), nameof(expression), "Unexpected expression form");
Contracts.CheckParam(methodCallExpression.Arguments[1] is ParameterExpression, nameof(expression), "Unexpected expression form");
Contracts.CheckParam(methodCallExpression.Arguments[1] == expression.Parameters[0], nameof(expression), "Unexpected expression form");

// Check the MethodInfo
Contracts.CheckParam(methodCallExpression.Object is ConstantExpression, nameof(expression), "Unexpected expression form");
Contracts.CheckParam(((ConstantExpression)methodCallExpression.Object).Type == typeof(MethodInfo), nameof(expression), "Unexpected expression form");

var methodInfo = (MethodInfo)((ConstantExpression)methodCallExpression.Object).Value;
Contracts.CheckParam(expression.Body is UnaryExpression, nameof(expression), "Unexpected expression form");
Contracts.CheckParam(((UnaryExpression)expression.Body).Operand is MethodCallExpression, nameof(expression), "Unexpected expression form");

return new FuncInstanceMethodInfo1<TTarget, TResult>(methodInfo);
}
}
}
46 changes: 46 additions & 0 deletions src/Microsoft.ML.Core/Utilities/FuncMethodInfo1`1.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

#nullable enable

using System;
using System.Collections.Immutable;
using System.Reflection;
using Microsoft.ML.Runtime;

namespace Microsoft.ML.Internal.Utilities
{
/// <summary>
/// Represents the <see cref="MethodInfo"/> for a generic function corresponding to <see cref="Func{TResult}"/>,
/// with the following characteristics:
///
/// <list type="bullet">
/// <item><description>One generic type argument.</description></item>
/// <item><description>A return value of <typeparamref name="TResult"/>.</description></item>
/// </list>
/// </summary>
/// <typeparam name="TResult">The type of the return value of the method.</typeparam>
internal abstract class FuncMethodInfo1<TResult> : FuncMethodInfo<TResult>
{
private ImmutableDictionary<Type, MethodInfo> _instanceMethodInfo;

private protected FuncMethodInfo1(MethodInfo methodInfo)
: base(methodInfo)
{
_instanceMethodInfo = ImmutableDictionary<Type, MethodInfo>.Empty;

Contracts.CheckParam(GenericMethodDefinition.GetGenericArguments().Length == 1, nameof(methodInfo),
"Should have exactly one generic type parameter but does not");
}

public MethodInfo MakeGenericMethod(Type typeArg1)
{
return ImmutableInterlocked.GetOrAdd(
ref _instanceMethodInfo,
typeArg1,
(typeArg, methodInfo) => methodInfo.MakeGenericMethod(typeArg),
GenericMethodDefinition);
}
}
}
25 changes: 25 additions & 0 deletions src/Microsoft.ML.Core/Utilities/FuncMethodInfo`1.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

#nullable enable

using System.Reflection;
using Microsoft.ML.Runtime;

namespace Microsoft.ML.Internal.Utilities
{
internal abstract class FuncMethodInfo<TResult>
{
private protected FuncMethodInfo(MethodInfo methodInfo)
{
Contracts.CheckValue(methodInfo, nameof(methodInfo));
Contracts.CheckParam(methodInfo.IsGenericMethod, nameof(methodInfo), "Should be generic but is not");

GenericMethodDefinition = methodInfo.GetGenericMethodDefinition();
Contracts.CheckParam(typeof(TResult).IsAssignableFrom(GenericMethodDefinition.ReturnType), nameof(methodInfo), "Cannot be generic on return type");
}

protected MethodInfo GenericMethodDefinition { get; }
}
}
32 changes: 32 additions & 0 deletions src/Microsoft.ML.Core/Utilities/FuncStaticMethodInfo1`1.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

#nullable enable

using System;
using System.Reflection;
using Microsoft.ML.Runtime;

namespace Microsoft.ML.Internal.Utilities
{
/// <summary>
/// Represents the <see cref="MethodInfo"/> for a generic function corresponding to <see cref="Func{TResult}"/>,
/// with the following characteristics:
///
/// <list type="bullet">
/// <item><description>The method is static.</description></item>
/// <item><description>One generic type argument.</description></item>
/// <item><description>A return value of <typeparamref name="TResult"/>.</description></item>
/// </list>
/// </summary>
/// <typeparam name="TResult">The type of the return value of the method.</typeparam>
internal sealed class FuncStaticMethodInfo1<TResult> : FuncMethodInfo1<TResult>
{
public FuncStaticMethodInfo1(Func<TResult> function)
: base(function.Method)
{
Contracts.CheckParam(GenericMethodDefinition.IsStatic, nameof(function), "Should be a static method");
}
}
}
46 changes: 29 additions & 17 deletions src/Microsoft.ML.Core/Utilities/Utils.cs
Original file line number Diff line number Diff line change
Expand Up @@ -988,20 +988,32 @@ private static MethodInfo MarshalInvokeCheckAndCreate<TRet>(Type[] genArgs, Dele
/// Because it is strongly typed, this can only be applied to methods whose return type
/// is known at compile time, that is, that do not depend on the type parameter of the method itself.
/// </summary>
/// <typeparam name="TRet">The return value</typeparam>
/// <typeparam name="TTarget">The type of the receiver of the instance method.</typeparam>
/// <typeparam name="TResult">The type of the return value of the method.</typeparam>
/// <param name="func">A delegate that should be a generic method with a single type parameter.
/// The generic method definition will be extracted, then a new method will be created with the
/// given type parameter, then the method will be invoked.</param>
/// <param name="target">The target of the invocation.</param>
/// <param name="genArg">The new type parameter for the generic method</param>
/// <returns>The return value of the invoked function</returns>
public static TRet MarshalInvoke<TRet>(Func<TRet> func, Type genArg)
public static TResult MarshalInvoke<TTarget, TResult>(FuncInstanceMethodInfo1<TTarget, TResult> func, TTarget target, Type genArg)
where TTarget : class
{
var meth = MarshalInvokeCheckAndCreate<TRet>(genArg, func);
return (TRet)meth.Invoke(func.Target, null);
var meth = func.MakeGenericMethod(genArg);
return (TResult)meth.Invoke(target, null);
}

/// <summary>
/// A static version of <see cref="MarshalInvoke{TTarget, TResult}(FuncInstanceMethodInfo1{TTarget, TResult}, TTarget, Type)"/>.
/// </summary>
public static TResult MarshalInvoke<TResult>(FuncStaticMethodInfo1<TResult> func, Type genArg)
{
var meth = func.MakeGenericMethod(genArg);
return (TResult)meth.Invoke(null, null);
}

/// <summary>
/// A one-argument version of <see cref="MarshalInvoke{TRet}"/>.
/// A one-argument version of <see cref="MarshalInvoke{TTarget, TResult}(FuncInstanceMethodInfo1{TTarget, TResult}, TTarget, Type)"/>.
/// </summary>
public static TRet MarshalInvoke<TArg1, TRet>(Func<TArg1, TRet> func, Type genArg, TArg1 arg1)
{
Expand All @@ -1010,7 +1022,7 @@ public static TRet MarshalInvoke<TArg1, TRet>(Func<TArg1, TRet> func, Type genAr
}

/// <summary>
/// A two-argument version of <see cref="MarshalInvoke{TRet}"/>.
/// A two-argument version of <see cref="MarshalInvoke{TTarget, TResult}(FuncInstanceMethodInfo1{TTarget, TResult}, TTarget, Type)"/>.
/// </summary>
public static TRet MarshalInvoke<TArg1, TArg2, TRet>(Func<TArg1, TArg2, TRet> func, Type genArg, TArg1 arg1, TArg2 arg2)
{
Expand All @@ -1019,7 +1031,7 @@ public static TRet MarshalInvoke<TArg1, TArg2, TRet>(Func<TArg1, TArg2, TRet> fu
}

/// <summary>
/// A three-argument version of <see cref="MarshalInvoke{TRet}"/>.
/// A three-argument version of <see cref="MarshalInvoke{TTarget, TResult}(FuncInstanceMethodInfo1{TTarget, TResult}, TTarget, Type)"/>.
/// </summary>
public static TRet MarshalInvoke<TArg1, TArg2, TArg3, TRet>(Func<TArg1, TArg2, TArg3, TRet> func, Type genArg,
TArg1 arg1, TArg2 arg2, TArg3 arg3)
Expand All @@ -1029,7 +1041,7 @@ public static TRet MarshalInvoke<TArg1, TArg2, TArg3, TRet>(Func<TArg1, TArg2, T
}

/// <summary>
/// A four-argument version of <see cref="MarshalInvoke{TRet}"/>.
/// A four-argument version of <see cref="MarshalInvoke{TTarget, TResult}(FuncInstanceMethodInfo1{TTarget, TResult}, TTarget, Type)"/>.
/// </summary>
public static TRet MarshalInvoke<TArg1, TArg2, TArg3, TArg4, TRet>(Func<TArg1, TArg2, TArg3, TArg4, TRet> func,
Type genArg, TArg1 arg1, TArg2 arg2, TArg3 arg3, TArg4 arg4)
Expand All @@ -1039,7 +1051,7 @@ public static TRet MarshalInvoke<TArg1, TArg2, TArg3, TArg4, TRet>(Func<TArg1, T
}

/// <summary>
/// A five-argument version of <see cref="MarshalInvoke{TRet}"/>.
/// A five-argument version of <see cref="MarshalInvoke{TTarget, TResult}(FuncInstanceMethodInfo1{TTarget, TResult}, TTarget, Type)"/>.
/// </summary>
public static TRet MarshalInvoke<TArg1, TArg2, TArg3, TArg4, TArg5, TRet>(Func<TArg1, TArg2, TArg3, TArg4, TArg5, TRet> func,
Type genArg, TArg1 arg1, TArg2 arg2, TArg3 arg3, TArg4 arg4, TArg5 arg5)
Expand All @@ -1049,7 +1061,7 @@ public static TRet MarshalInvoke<TArg1, TArg2, TArg3, TArg4, TArg5, TRet>(Func<T
}

/// <summary>
/// A six-argument version of <see cref="MarshalInvoke{TRet}"/>.
/// A six-argument version of <see cref="MarshalInvoke{TTarget, TResult}(FuncInstanceMethodInfo1{TTarget, TResult}, TTarget, Type)"/>.
/// </summary>
public static TRet MarshalInvoke<TArg1, TArg2, TArg3, TArg4, TArg5, TArg6, TRet>(Func<TArg1, TArg2, TArg3, TArg4, TArg5, TArg6, TRet> func,
Type genArg, TArg1 arg1, TArg2 arg2, TArg3 arg3, TArg4 arg4, TArg5 arg5, TArg6 arg6)
Expand All @@ -1059,7 +1071,7 @@ public static TRet MarshalInvoke<TArg1, TArg2, TArg3, TArg4, TArg5, TArg6, TRet>
}

/// <summary>
/// A seven-argument version of <see cref="MarshalInvoke{TRet}"/>.
/// A seven-argument version of <see cref="MarshalInvoke{TTarget, TResult}(FuncInstanceMethodInfo1{TTarget, TResult}, TTarget, Type)"/>.
/// </summary>
public static TRet MarshalInvoke<TArg1, TArg2, TArg3, TArg4, TArg5, TArg6, TArg7, TRet>(Func<TArg1, TArg2, TArg3, TArg4, TArg5, TArg6, TArg7, TRet> func,
Type genArg, TArg1 arg1, TArg2 arg2, TArg3 arg3, TArg4 arg4, TArg5 arg5, TArg6 arg6, TArg7 arg7)
Expand All @@ -1069,7 +1081,7 @@ public static TRet MarshalInvoke<TArg1, TArg2, TArg3, TArg4, TArg5, TArg6, TArg7
}

/// <summary>
/// An eight-argument version of <see cref="MarshalInvoke{TRet}"/>.
/// An eight-argument version of <see cref="MarshalInvoke{TTarget, TResult}(FuncInstanceMethodInfo1{TTarget, TResult}, TTarget, Type)"/>.
/// </summary>
public static TRet MarshalInvoke<TArg1, TArg2, TArg3, TArg4, TArg5, TArg6, TArg7, TArg8, TRet>(Func<TArg1, TArg2, TArg3, TArg4, TArg5, TArg6, TArg7, TArg8, TRet> func,
Type genArg, TArg1 arg1, TArg2 arg2, TArg3 arg3, TArg4 arg4, TArg5 arg5, TArg6 arg6, TArg7 arg7, TArg8 arg8)
Expand All @@ -1079,7 +1091,7 @@ public static TRet MarshalInvoke<TArg1, TArg2, TArg3, TArg4, TArg5, TArg6, TArg7
}

/// <summary>
/// A nine-argument version of <see cref="MarshalInvoke{TRet}"/>.
/// A nine-argument version of <see cref="MarshalInvoke{TTarget, TResult}(FuncInstanceMethodInfo1{TTarget, TResult}, TTarget, Type)"/>.
/// </summary>
public static TRet MarshalInvoke<TArg1, TArg2, TArg3, TArg4, TArg5, TArg6, TArg7, TArg8, TArg9, TRet>(
Func<TArg1, TArg2, TArg3, TArg4, TArg5, TArg6, TArg7, TArg8, TArg9, TRet> func,
Expand All @@ -1090,7 +1102,7 @@ public static TRet MarshalInvoke<TArg1, TArg2, TArg3, TArg4, TArg5, TArg6, TArg7
}

/// <summary>
/// A ten-argument version of <see cref="MarshalInvoke{TRet}"/>.
/// A ten-argument version of <see cref="MarshalInvoke{TTarget, TResult}(FuncInstanceMethodInfo1{TTarget, TResult}, TTarget, Type)"/>.
/// </summary>
public static TRet MarshalInvoke<TArg1, TArg2, TArg3, TArg4, TArg5, TArg6, TArg7, TArg8, TArg9, TArg10, TRet>(
Func<TArg1, TArg2, TArg3, TArg4, TArg5, TArg6, TArg7, TArg8, TArg9, TArg10, TRet> func,
Expand All @@ -1101,7 +1113,7 @@ public static TRet MarshalInvoke<TArg1, TArg2, TArg3, TArg4, TArg5, TArg6, TArg7
}

/// <summary>
/// A 1 argument and n type version of <see cref="MarshalInvoke{TRet}"/>.
/// A 1 argument and n type version of <see cref="MarshalInvoke{TTarget, TResult}(FuncInstanceMethodInfo1{TTarget, TResult}, TTarget, Type)"/>.
/// </summary>
public static TRet MarshalInvoke<TArg1, TRet>(
Func<TArg1, TRet> func,
Expand All @@ -1112,7 +1124,7 @@ public static TRet MarshalInvoke<TArg1, TRet>(
}

/// <summary>
/// A 2 argument and n type version of <see cref="MarshalInvoke{TRet}"/>.
/// A 2 argument and n type version of <see cref="MarshalInvoke{TTarget, TResult}(FuncInstanceMethodInfo1{TTarget, TResult}, TTarget, Type)"/>.
/// </summary>
public static TRet MarshalInvoke<TArg1, TArg2, TRet>(
Func<TArg1, TArg2, TRet> func,
Expand Down Expand Up @@ -1147,7 +1159,7 @@ private static MethodInfo MarshalActionInvokeCheckAndCreate(Type[] typeArguments
}

/// <summary>
/// This is akin to <see cref="MarshalInvoke{TRet}(Func{TRet}, Type)"/>, except applied to
/// This is akin to <see cref="MarshalInvoke{TTarget, TResult}(FuncInstanceMethodInfo1{TTarget, TResult}, TTarget, Type)"/>, except applied to
/// <see cref="Action"/> instead of <see cref="Func{TRet}"/>.
/// </summary>
/// <param name="act">A delegate that should be a generic method with a single type parameter.
Expand Down
5 changes: 4 additions & 1 deletion src/Microsoft.ML.Data/DataLoadSave/Binary/BinaryLoader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1240,6 +1240,9 @@ public DataViewRowCursor[] GetRowCursorSet(IEnumerable<DataViewSchema.Column> co

private sealed class Cursor : RootCursorBase
{
private static readonly FuncInstanceMethodInfo1<Cursor, Delegate> _noRowGetterMethodInfo
= FuncInstanceMethodInfo1<Cursor, Delegate>.Create(target => target.NoRowGetter<int>);

private readonly BinaryLoader _parent;
private readonly int[] _colToActivesIndex;
private readonly TableOfContentsEntry[] _actives;
Expand Down Expand Up @@ -2071,7 +2074,7 @@ public override ValueGetter<TValue> GetGetter<TValue>(DataViewSchema.Column colu
/// a delegate that simply always throws.
/// </summary>
private Delegate GetNoRowGetter(DataViewType type)
=> Utils.MarshalInvoke(NoRowGetter<int>, type.RawType);
=> Utils.MarshalInvoke(_noRowGetterMethodInfo, this, type.RawType);

private Delegate NoRowGetter<T>()
{
Expand Down
Loading