4740 lines
224 KiB
C#
4740 lines
224 KiB
C#
// <auto-generated/>
|
|
/*
|
|
The MIT License (MIT)
|
|
|
|
Copyright (c) 2016-2020 Maksim Volkau
|
|
|
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
of this software and associated documentation files (the "Software"), to deal
|
|
in the Software without restriction, including without limitation the rights
|
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
copies of the Software, and to permit persons to whom the Software is
|
|
furnished to do so, subject to the following conditions:
|
|
|
|
The above copyright notice and this permission notice shall be included AddOrUpdateServiceFactory
|
|
all copies or substantial portions of the Software.
|
|
|
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
THE SOFTWARE.
|
|
*/
|
|
|
|
// ReSharper disable CoVariantArrayConversion
|
|
|
|
/*
|
|
// Lists the target platforms that are Not supported by FEC - simplifies the direct referencing of Expression.cs file
|
|
*/
|
|
#if !PCL && !NET35 && !NET40 && !NET403 && !NETSTANDARD1_0 && !NETSTANDARD1_1 && !NETSTANDARD1_2 && !NETCOREAPP1_0 && !NETCOREAPP1_1
|
|
#define SUPPORTS_FAST_EXPRESSION_COMPILER
|
|
#endif
|
|
|
|
#if !SUPPORTS_FAST_EXPRESSION_COMPILER
|
|
namespace FastExpressionCompiler.LightExpression
|
|
{
|
|
using System;
|
|
using System.Linq.Expressions;
|
|
|
|
/// <summary>Polyfill for absence of FastExpressionCompiler: https://github.com/dadhi/FastExpressionCompiler </summary>
|
|
public static class ExpressionCompiler
|
|
{
|
|
internal static object TryCompileBoundToFirstClosureParam(Type delegateType,
|
|
Expression bodyExpr, ParameterExpression[] paramExprs, Type[] closurePlusParamTypes, Type returnType) => null;
|
|
|
|
internal static Func<T1, R> CompileFast<T1, R>(this Expression<Func<T1, R>> lambdaExpr) => lambdaExpr.Compile();
|
|
|
|
internal class ArrayClosure {}
|
|
}
|
|
}
|
|
#else
|
|
// ReSharper disable CoVariantArrayConversion
|
|
namespace FastExpressionCompiler.LightExpression
|
|
{
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Linq.Expressions;
|
|
using System.Reflection;
|
|
using System.Reflection.Emit;
|
|
using System.Threading;
|
|
|
|
/// <summary>Compiles expression to delegate ~20 times faster than Expression.Compile.
|
|
/// Partial to extend with your things when used as source file.</summary>
|
|
// ReSharper disable once PartialTypeWithSinglePart
|
|
public static partial class ExpressionCompiler
|
|
{
|
|
#region Expression.CompileFast overloads for Delegate, Func, and Action
|
|
|
|
/// <summary>Compiles lambda expression to TDelegate type. Use ifFastFailedReturnNull parameter to Not fallback to Expression.Compile, useful for testing.</summary>
|
|
public static TDelegate CompileFast<TDelegate>(this LambdaExpression lambdaExpr,
|
|
bool ifFastFailedReturnNull = false) where TDelegate : class =>
|
|
(TDelegate)(TryCompileBoundToFirstClosureParam(typeof(TDelegate) == typeof(Delegate) ? lambdaExpr.Type : typeof(TDelegate),
|
|
lambdaExpr.Body, lambdaExpr.Parameters, GetClosureTypeToParamTypes(lambdaExpr.Parameters), lambdaExpr.ReturnType)
|
|
?? (ifFastFailedReturnNull ? null : lambdaExpr.CompileSys()));
|
|
|
|
/// Compiles a static method to the passed IL Generator.
|
|
/// Could be used as alternative for `CompileToMethod` like this <code><![CDATA[funcExpr.CompileFastToIL(methodBuilder.GetILGenerator())]]></code>.
|
|
/// Check `IssueTests.Issue179_Add_something_like_LambdaExpression_CompileToMethod.cs` for example.
|
|
public static bool CompileFastToIL(this LambdaExpression lambdaExpr, ILGenerator il, bool ifFastFailedReturnNull = false)
|
|
{
|
|
var closureInfo = new ClosureInfo(ClosureStatus.ShouldBeStaticMethod);
|
|
|
|
var parentFlags = lambdaExpr.ReturnType == typeof(void) ? ParentFlags.IgnoreResult : ParentFlags.Empty;
|
|
if (!EmittingVisitor.TryEmit(lambdaExpr.Body, lambdaExpr.Parameters, il, ref closureInfo, parentFlags))
|
|
return false;
|
|
|
|
il.Emit(OpCodes.Ret);
|
|
return true;
|
|
}
|
|
|
|
/// <summary>Compiles lambda expression to delegate. Use ifFastFailedReturnNull parameter to Not fallback to Expression.Compile, useful for testing.</summary>
|
|
public static Delegate CompileFast(this LambdaExpression lambdaExpr, bool ifFastFailedReturnNull = false) =>
|
|
(Delegate)TryCompileBoundToFirstClosureParam(lambdaExpr.Type, lambdaExpr.Body, lambdaExpr.Parameters,
|
|
GetClosureTypeToParamTypes(lambdaExpr.Parameters), lambdaExpr.ReturnType)
|
|
?? (ifFastFailedReturnNull ? null : lambdaExpr.CompileSys());
|
|
|
|
/// <summary>Unifies Compile for System.Linq.Expressions and FEC.LightExpression</summary>
|
|
public static TDelegate CompileSys<TDelegate>(this Expression<TDelegate> lambdaExpr) where TDelegate : class =>
|
|
lambdaExpr
|
|
.ToLambdaExpression()
|
|
.Compile();
|
|
|
|
/// <summary>Unifies Compile for System.Linq.Expressions and FEC.LightExpression</summary>
|
|
public static Delegate CompileSys(this LambdaExpression lambdaExpr) =>
|
|
lambdaExpr
|
|
.ToLambdaExpression()
|
|
.Compile();
|
|
|
|
/// <summary>Compiles lambda expression to TDelegate type. Use ifFastFailedReturnNull parameter to Not fallback to Expression.Compile, useful for testing.</summary>
|
|
public static TDelegate CompileFast<TDelegate>(this Expression<TDelegate> lambdaExpr,
|
|
bool ifFastFailedReturnNull = false)
|
|
where TDelegate : class => ((LambdaExpression)lambdaExpr).CompileFast<TDelegate>(ifFastFailedReturnNull);
|
|
|
|
/// <summary>Compiles lambda expression to delegate. Use ifFastFailedReturnNull parameter to Not fallback to Expression.Compile, useful for testing.</summary>
|
|
public static Func<R> CompileFast<R>(this Expression<Func<R>> lambdaExpr,
|
|
bool ifFastFailedReturnNull = false) =>
|
|
(Func<R>)TryCompileBoundToFirstClosureParam(typeof(Func<R>),
|
|
lambdaExpr.Body, lambdaExpr.Parameters, _closureAsASingleParamType, typeof(R))
|
|
?? (ifFastFailedReturnNull ? null : lambdaExpr.CompileSys());
|
|
|
|
/// <summary>Compiles lambda expression to delegate. Use ifFastFailedReturnNull parameter to Not fallback to Expression.Compile, useful for testing.</summary>
|
|
public static Func<T1, R> CompileFast<T1, R>(this Expression<Func<T1, R>> lambdaExpr,
|
|
bool ifFastFailedReturnNull = false) =>
|
|
(Func<T1, R>)TryCompileBoundToFirstClosureParam(typeof(Func<T1, R>),
|
|
lambdaExpr.Body, lambdaExpr.Parameters, new[] { typeof(ArrayClosure), typeof(T1) }, typeof(R))
|
|
?? (ifFastFailedReturnNull ? null : lambdaExpr.CompileSys());
|
|
|
|
/// <summary>Compiles lambda expression to TDelegate type. Use ifFastFailedReturnNull parameter to Not fallback to Expression.Compile, useful for testing.</summary>
|
|
public static Func<T1, T2, R> CompileFast<T1, T2, R>(this Expression<Func<T1, T2, R>> lambdaExpr,
|
|
bool ifFastFailedReturnNull = false) =>
|
|
(Func<T1, T2, R>)TryCompileBoundToFirstClosureParam(typeof(Func<T1, T2, R>),
|
|
lambdaExpr.Body, lambdaExpr.Parameters, new[] { typeof(ArrayClosure), typeof(T1), typeof(T2) },
|
|
typeof(R))
|
|
?? (ifFastFailedReturnNull ? null : lambdaExpr.CompileSys());
|
|
|
|
/// <summary>Compiles lambda expression to delegate. Use ifFastFailedReturnNull parameter to Not fallback to Expression.Compile, useful for testing.</summary>
|
|
public static Func<T1, T2, T3, R> CompileFast<T1, T2, T3, R>(
|
|
this Expression<Func<T1, T2, T3, R>> lambdaExpr, bool ifFastFailedReturnNull = false) =>
|
|
(Func<T1, T2, T3, R>)TryCompileBoundToFirstClosureParam(typeof(Func<T1, T2, T3, R>),
|
|
lambdaExpr.Body, lambdaExpr.Parameters, new[] { typeof(ArrayClosure), typeof(T1), typeof(T2), typeof(T3) }, typeof(R))
|
|
?? (ifFastFailedReturnNull ? null : lambdaExpr.CompileSys());
|
|
|
|
/// <summary>Compiles lambda expression to TDelegate type. Use ifFastFailedReturnNull parameter to Not fallback to Expression.Compile, useful for testing.</summary>
|
|
public static Func<T1, T2, T3, T4, R> CompileFast<T1, T2, T3, T4, R>(
|
|
this Expression<Func<T1, T2, T3, T4, R>> lambdaExpr, bool ifFastFailedReturnNull = false) =>
|
|
(Func<T1, T2, T3, T4, R>)TryCompileBoundToFirstClosureParam(typeof(Func<T1, T2, T3, T4, R>),
|
|
lambdaExpr.Body, lambdaExpr.Parameters,
|
|
new[] { typeof(ArrayClosure), typeof(T1), typeof(T2), typeof(T3), typeof(T4) }, typeof(R))
|
|
?? (ifFastFailedReturnNull ? null : lambdaExpr.CompileSys());
|
|
|
|
/// <summary>Compiles lambda expression to delegate. Use ifFastFailedReturnNull parameter to Not fallback to Expression.Compile, useful for testing.</summary>
|
|
public static Func<T1, T2, T3, T4, T5, R> CompileFast<T1, T2, T3, T4, T5, R>(
|
|
this Expression<Func<T1, T2, T3, T4, T5, R>> lambdaExpr, bool ifFastFailedReturnNull = false) =>
|
|
(Func<T1, T2, T3, T4, T5, R>)TryCompileBoundToFirstClosureParam(typeof(Func<T1, T2, T3, T4, T5, R>),
|
|
lambdaExpr.Body, lambdaExpr.Parameters,
|
|
new[] { typeof(ArrayClosure), typeof(T1), typeof(T2), typeof(T3), typeof(T4), typeof(T5) }, typeof(R))
|
|
?? (ifFastFailedReturnNull ? null : lambdaExpr.CompileSys());
|
|
|
|
/// <summary>Compiles lambda expression to delegate. Use ifFastFailedReturnNull parameter to Not fallback to Expression.Compile, useful for testing.</summary>
|
|
public static Func<T1, T2, T3, T4, T5, T6, R> CompileFast<T1, T2, T3, T4, T5, T6, R>(
|
|
this Expression<Func<T1, T2, T3, T4, T5, T6, R>> lambdaExpr, bool ifFastFailedReturnNull = false) =>
|
|
(Func<T1, T2, T3, T4, T5, T6, R>)TryCompileBoundToFirstClosureParam(typeof(Func<T1, T2, T3, T4, T5, T6, R>),
|
|
lambdaExpr.Body, lambdaExpr.Parameters,
|
|
new[] { typeof(ArrayClosure), typeof(T1), typeof(T2), typeof(T3), typeof(T4), typeof(T5), typeof(T6) }, typeof(R))
|
|
?? (ifFastFailedReturnNull ? null : lambdaExpr.CompileSys());
|
|
|
|
/// <summary>Compiles lambda expression to delegate. Use ifFastFailedReturnNull parameter to Not fallback to Expression.Compile, useful for testing.</summary>
|
|
public static Action CompileFast(this Expression<Action> lambdaExpr, bool ifFastFailedReturnNull = false) =>
|
|
(Action)TryCompileBoundToFirstClosureParam(typeof(Action),
|
|
lambdaExpr.Body, lambdaExpr.Parameters, _closureAsASingleParamType, typeof(void))
|
|
?? (ifFastFailedReturnNull ? null : lambdaExpr.CompileSys());
|
|
|
|
/// <summary>Compiles lambda expression to delegate. Use ifFastFailedReturnNull parameter to Not fallback to Expression.Compile, useful for testing.</summary>
|
|
public static Action<T1> CompileFast<T1>(this Expression<Action<T1>> lambdaExpr,
|
|
bool ifFastFailedReturnNull = false) =>
|
|
(Action<T1>)TryCompileBoundToFirstClosureParam(typeof(Action<T1>),
|
|
lambdaExpr.Body, lambdaExpr.Parameters, new[] { typeof(ArrayClosure), typeof(T1) }, typeof(void))
|
|
?? (ifFastFailedReturnNull ? null : lambdaExpr.CompileSys());
|
|
|
|
/// <summary>Compiles lambda expression to delegate. Use ifFastFailedReturnNull parameter to Not fallback to Expression.Compile, useful for testing.</summary>
|
|
public static Action<T1, T2> CompileFast<T1, T2>(this Expression<Action<T1, T2>> lambdaExpr,
|
|
bool ifFastFailedReturnNull = false) =>
|
|
(Action<T1, T2>)TryCompileBoundToFirstClosureParam(typeof(Action<T1, T2>),
|
|
lambdaExpr.Body, lambdaExpr.Parameters, new[] { typeof(ArrayClosure), typeof(T1), typeof(T2) },
|
|
typeof(void))
|
|
?? (ifFastFailedReturnNull ? null : lambdaExpr.CompileSys());
|
|
|
|
/// <summary>Compiles lambda expression to delegate. Use ifFastFailedReturnNull parameter to Not fallback to Expression.Compile, useful for testing.</summary>
|
|
public static Action<T1, T2, T3> CompileFast<T1, T2, T3>(this Expression<Action<T1, T2, T3>> lambdaExpr,
|
|
bool ifFastFailedReturnNull = false) =>
|
|
(Action<T1, T2, T3>)TryCompileBoundToFirstClosureParam(typeof(Action<T1, T2, T3>),
|
|
lambdaExpr.Body, lambdaExpr.Parameters,
|
|
new[] { typeof(ArrayClosure), typeof(T1), typeof(T2), typeof(T3) }, typeof(void))
|
|
?? (ifFastFailedReturnNull ? null : lambdaExpr.CompileSys());
|
|
|
|
/// <summary>Compiles lambda expression to delegate. Use ifFastFailedReturnNull parameter to Not fallback to Expression.Compile, useful for testing.</summary>
|
|
public static Action<T1, T2, T3, T4> CompileFast<T1, T2, T3, T4>(
|
|
this Expression<Action<T1, T2, T3, T4>> lambdaExpr, bool ifFastFailedReturnNull = false) =>
|
|
(Action<T1, T2, T3, T4>)TryCompileBoundToFirstClosureParam(typeof(Action<T1, T2, T3, T4>),
|
|
lambdaExpr.Body, lambdaExpr.Parameters,
|
|
new[] { typeof(ArrayClosure), typeof(T1), typeof(T2), typeof(T3), typeof(T4) }, typeof(void))
|
|
?? (ifFastFailedReturnNull ? null : lambdaExpr.CompileSys());
|
|
|
|
/// <summary>Compiles lambda expression to delegate. Use ifFastFailedReturnNull parameter to Not fallback to Expression.Compile, useful for testing.</summary>
|
|
public static Action<T1, T2, T3, T4, T5> CompileFast<T1, T2, T3, T4, T5>(
|
|
this Expression<Action<T1, T2, T3, T4, T5>> lambdaExpr, bool ifFastFailedReturnNull = false) =>
|
|
(Action<T1, T2, T3, T4, T5>)TryCompileBoundToFirstClosureParam(typeof(Action<T1, T2, T3, T4, T5>),
|
|
lambdaExpr.Body, lambdaExpr.Parameters,
|
|
new[] { typeof(ArrayClosure), typeof(T1), typeof(T2), typeof(T3), typeof(T4), typeof(T5) }, typeof(void))
|
|
?? (ifFastFailedReturnNull ? null : lambdaExpr.CompileSys());
|
|
|
|
/// <summary>Compiles lambda expression to delegate. Use ifFastFailedReturnNull parameter to Not fallback to Expression.Compile, useful for testing.</summary>
|
|
public static Action<T1, T2, T3, T4, T5, T6> CompileFast<T1, T2, T3, T4, T5, T6>(
|
|
this Expression<Action<T1, T2, T3, T4, T5, T6>> lambdaExpr, bool ifFastFailedReturnNull = false) =>
|
|
(Action<T1, T2, T3, T4, T5, T6>)TryCompileBoundToFirstClosureParam(typeof(Action<T1, T2, T3, T4, T5, T6>),
|
|
lambdaExpr.Body, lambdaExpr.Parameters,
|
|
new[] { typeof(ArrayClosure), typeof(T1), typeof(T2), typeof(T3), typeof(T4), typeof(T5), typeof(T6) }, typeof(void))
|
|
?? (ifFastFailedReturnNull ? null : lambdaExpr.CompileSys());
|
|
|
|
#endregion
|
|
|
|
/// <summary>Tries to compile lambda expression to <typeparamref name="TDelegate"/></summary>
|
|
public static TDelegate TryCompile<TDelegate>(this LambdaExpression lambdaExpr) where TDelegate : class =>
|
|
(TDelegate)TryCompileBoundToFirstClosureParam(typeof(TDelegate) == typeof(Delegate) ? lambdaExpr.Type : typeof(TDelegate),
|
|
lambdaExpr.Body, lambdaExpr.Parameters, GetClosureTypeToParamTypes(lambdaExpr.Parameters), lambdaExpr.ReturnType);
|
|
|
|
/// <summary>Tries to compile lambda expression to <typeparamref name="TDelegate"/>
|
|
/// with the provided closure object and constant expressions (or lack there of) -
|
|
/// Constant expression should be the in order of Fields in closure object!
|
|
/// Note 1: Use it on your own risk - FEC won't verify the expression is compile-able with passed closure, it is up to you!
|
|
/// Note 2: The expression with NESTED LAMBDA IS NOT SUPPORTED!
|
|
/// Note 3: `Label` and `GoTo` are not supported in this case, because they need first round to collect out-of-order labels</summary>
|
|
public static TDelegate TryCompileWithPreCreatedClosure<TDelegate>(this LambdaExpression lambdaExpr,
|
|
params ConstantExpression[] closureConstantsExprs)
|
|
where TDelegate : class
|
|
{
|
|
var constValues = new object[closureConstantsExprs.Length];
|
|
for (var i = 0; i < constValues.Length; i++)
|
|
constValues[i] = closureConstantsExprs[i].Value;
|
|
|
|
var closureInfo = new ClosureInfo(ClosureStatus.UserProvided | ClosureStatus.HasClosure, constValues);
|
|
|
|
var closurePlusParamTypes = GetClosureTypeToParamTypes(lambdaExpr.Parameters);
|
|
var method = new DynamicMethod(string.Empty, lambdaExpr.ReturnType, closurePlusParamTypes,
|
|
typeof(ExpressionCompiler), skipVisibility: true);
|
|
|
|
var il = method.GetILGenerator();
|
|
|
|
EmittingVisitor.EmitLoadConstantsAndNestedLambdasIntoVars(il, ref closureInfo);
|
|
|
|
var parentFlags = lambdaExpr.ReturnType == typeof(void) ? ParentFlags.IgnoreResult : ParentFlags.Empty;
|
|
if (!EmittingVisitor.TryEmit(lambdaExpr.Body, lambdaExpr.Parameters, il, ref closureInfo, parentFlags))
|
|
return null;
|
|
il.Emit(OpCodes.Ret);
|
|
|
|
var delegateType = typeof(TDelegate) != typeof(Delegate) ? typeof(TDelegate) : lambdaExpr.Type;
|
|
var @delegate = (TDelegate)(object)method.CreateDelegate(delegateType, new ArrayClosure(constValues));
|
|
ReturnClosureTypeToParamTypesToPool(closurePlusParamTypes);
|
|
return @delegate;
|
|
}
|
|
|
|
/// <summary>Tries to compile expression to "static" delegate, skipping the step of collecting the closure object.</summary>
|
|
public static TDelegate TryCompileWithoutClosure<TDelegate>(this LambdaExpression lambdaExpr)
|
|
where TDelegate : class
|
|
{
|
|
var closureInfo = new ClosureInfo(ClosureStatus.UserProvided);
|
|
var closurePlusParamTypes = GetClosureTypeToParamTypes(lambdaExpr.Parameters);
|
|
|
|
var method = new DynamicMethod(string.Empty, lambdaExpr.ReturnType, closurePlusParamTypes,
|
|
typeof(ArrayClosure), skipVisibility: true);
|
|
|
|
var il = method.GetILGenerator();
|
|
var parentFlags = lambdaExpr.ReturnType == typeof(void) ? ParentFlags.IgnoreResult : ParentFlags.Empty;
|
|
if (!EmittingVisitor.TryEmit(lambdaExpr.Body, lambdaExpr.Parameters, il, ref closureInfo, parentFlags))
|
|
return null;
|
|
il.Emit(OpCodes.Ret);
|
|
|
|
var delegateType = typeof(TDelegate) != typeof(Delegate) ? typeof(TDelegate) : lambdaExpr.Type;
|
|
var @delegate = (TDelegate)(object)method.CreateDelegate(delegateType, EmptyArrayClosure);
|
|
ReturnClosureTypeToParamTypesToPool(closurePlusParamTypes);
|
|
return @delegate;
|
|
}
|
|
|
|
#region Obsolete
|
|
|
|
/// Obsolete
|
|
[Obsolete("Not used - candidate for removal")]
|
|
public static TDelegate TryCompile<TDelegate>(
|
|
Expression bodyExpr, IReadOnlyList<ParameterExpression> paramExprs, Type[] paramTypes, Type returnType)
|
|
where TDelegate : class =>
|
|
(TDelegate)TryCompile(typeof(TDelegate), bodyExpr, paramExprs, paramTypes, returnType);
|
|
|
|
/// Obsolete
|
|
[Obsolete("Not used - candidate for removal")]
|
|
public static object TryCompile(Type delegateType,
|
|
Expression bodyExpr, IReadOnlyList<ParameterExpression> paramExprs, Type[] paramTypes, Type returnType) =>
|
|
TryCompileBoundToFirstClosureParam(
|
|
delegateType != typeof(Delegate) ? delegateType : Tools.GetFuncOrActionType(paramTypes, returnType),
|
|
bodyExpr, paramExprs, GetClosureTypeToParamTypes(paramExprs), returnType);
|
|
|
|
#endregion
|
|
|
|
internal static object TryCompileBoundToFirstClosureParam(Type delegateType,
|
|
Expression bodyExpr, IReadOnlyList<ParameterExpression> paramExprs, Type[] closurePlusParamTypes, Type returnType)
|
|
{
|
|
var closureInfo = new ClosureInfo(ClosureStatus.ToBeCollected);
|
|
if (!TryCollectBoundConstants(ref closureInfo, bodyExpr, paramExprs, false, ref closureInfo))
|
|
return null;
|
|
|
|
var nestedLambdas = closureInfo.NestedLambdas;
|
|
if (nestedLambdas.Length != 0)
|
|
for (var i = 0; i < nestedLambdas.Length; ++i)
|
|
if (!TryCompileNestedLambda(ref closureInfo, i))
|
|
return null;
|
|
|
|
var closure = (closureInfo.Status & ClosureStatus.HasClosure) == 0
|
|
? EmptyArrayClosure
|
|
: new ArrayClosure(closureInfo.GetArrayOfConstantsAndNestedLambdas());
|
|
|
|
var method = new DynamicMethod(string.Empty,
|
|
returnType, closurePlusParamTypes, typeof(ArrayClosure), true);
|
|
|
|
var il = method.GetILGenerator();
|
|
|
|
if (closure.ConstantsAndNestedLambdas != null)
|
|
EmittingVisitor.EmitLoadConstantsAndNestedLambdasIntoVars(il, ref closureInfo);
|
|
|
|
var parentFlags = returnType == typeof(void) ? ParentFlags.IgnoreResult : ParentFlags.Empty;
|
|
if (!EmittingVisitor.TryEmit(bodyExpr, paramExprs, il, ref closureInfo, parentFlags))
|
|
return null;
|
|
|
|
il.Emit(OpCodes.Ret);
|
|
|
|
var @delegate = method.CreateDelegate(delegateType, closure);
|
|
ReturnClosureTypeToParamTypesToPool(closurePlusParamTypes);
|
|
return @delegate;
|
|
}
|
|
|
|
private static Type[] PrependClosureTypeToParamTypes(IReadOnlyList<ParameterExpression> paramExprs)
|
|
{
|
|
var count = paramExprs.Count;
|
|
var closureAndParamTypes = new Type[count + 1];
|
|
closureAndParamTypes[0] = typeof(ArrayClosure);
|
|
for (var i = 0; i < count; i++)
|
|
{
|
|
var parameterExpr = paramExprs[i];
|
|
closureAndParamTypes[i + 1] = parameterExpr.IsByRef ? parameterExpr.Type.MakeByRefType() : parameterExpr.Type;
|
|
}
|
|
return closureAndParamTypes;
|
|
}
|
|
|
|
private static readonly Type[] _closureAsASingleParamType = { typeof(ArrayClosure) };
|
|
private static readonly Type[][] _closureTypePlusParamTypesPool = new Type[8][];
|
|
|
|
private static Type[] GetClosureTypeToParamTypes(IReadOnlyList<ParameterExpression> paramExprs)
|
|
{
|
|
var paramCount = paramExprs.Count;
|
|
if (paramCount == 0)
|
|
return _closureAsASingleParamType;
|
|
|
|
if (paramCount < 8)
|
|
{
|
|
var closureAndParamTypes = Interlocked.Exchange(ref _closureTypePlusParamTypesPool[paramCount], null);
|
|
if (closureAndParamTypes != null)
|
|
{
|
|
for (var i = 0; i < paramExprs.Count; i++)
|
|
{
|
|
var parameterExpr = paramExprs[i];
|
|
closureAndParamTypes[i + 1] = parameterExpr.IsByRef ? parameterExpr.Type.MakeByRefType() : parameterExpr.Type;
|
|
}
|
|
return closureAndParamTypes;
|
|
}
|
|
}
|
|
|
|
return PrependClosureTypeToParamTypes(paramExprs);
|
|
}
|
|
|
|
private static void ReturnClosureTypeToParamTypesToPool(Type[] closurePlusParamTypes)
|
|
{
|
|
var paramCount = closurePlusParamTypes.Length - 1;
|
|
if (paramCount != 0 && paramCount < 8)
|
|
Interlocked.Exchange(ref _closureTypePlusParamTypesPool[paramCount], closurePlusParamTypes);
|
|
}
|
|
|
|
private struct BlockInfo
|
|
{
|
|
public object VarExprs; // ParameterExpression | IReadOnlyList<ParameterExpression>
|
|
public int[] VarIndexes;
|
|
}
|
|
|
|
[Flags]
|
|
private enum ClosureStatus
|
|
{
|
|
ToBeCollected = 1,
|
|
UserProvided = 1 << 1,
|
|
HasClosure = 1 << 2,
|
|
ShouldBeStaticMethod = 1 << 3
|
|
}
|
|
|
|
/// Track the info required to build a closure object + some context information not directly related to closure.
|
|
private struct ClosureInfo
|
|
{
|
|
public bool LastEmitIsAddress;
|
|
|
|
/// Helpers to know if a Return GotoExpression's Label should be emitted.
|
|
/// First set bit is ContainsReturnGoto, the rest is ReturnLabelIndex
|
|
private int[] _tryCatchFinallyInfos;
|
|
public int CurrentTryCatchFinallyIndex;
|
|
|
|
/// Tracks the stack of blocks where are we in emit phase
|
|
private LiveCountArray<BlockInfo> _blockStack;
|
|
|
|
/// Dictionary for the used Labels in IL
|
|
private KeyValuePair<LabelTarget, Label?>[] _labels;
|
|
|
|
public ClosureStatus Status;
|
|
|
|
/// Constant expressions to find an index (by reference) of constant expression from compiled expression.
|
|
public LiveCountArray<object> Constants;
|
|
|
|
/// Parameters not passed through lambda parameter list But used inside lambda body.
|
|
/// The top expression should Not contain not passed parameters.
|
|
public ParameterExpression[] NonPassedParameters;
|
|
|
|
/// All nested lambdas recursively nested in expression
|
|
public NestedLambdaInfo[] NestedLambdas;
|
|
|
|
/// Constant usage count and variable index
|
|
public LiveCountArray<int> ConstantUsage;
|
|
|
|
/// Populates info directly with provided closure object and constants.
|
|
public ClosureInfo(ClosureStatus status, object[] constValues = null)
|
|
{
|
|
Status = status;
|
|
|
|
Constants = new LiveCountArray<object>(constValues ?? Tools.Empty<object>());
|
|
ConstantUsage = new LiveCountArray<int>(constValues == null ? Tools.Empty<int>() : new int[constValues.Length]);
|
|
|
|
NonPassedParameters = Tools.Empty<ParameterExpression>();
|
|
NestedLambdas = Tools.Empty<NestedLambdaInfo>();
|
|
|
|
LastEmitIsAddress = false;
|
|
CurrentTryCatchFinallyIndex = -1;
|
|
_tryCatchFinallyInfos = null;
|
|
_labels = null;
|
|
_blockStack = new LiveCountArray<BlockInfo>(Tools.Empty<BlockInfo>());
|
|
}
|
|
|
|
public void AddConstant(object value)
|
|
{
|
|
Status |= ClosureStatus.HasClosure;
|
|
|
|
var constItems = Constants.Items;
|
|
var constIndex = Constants.Count - 1;
|
|
while (constIndex != -1 && !ReferenceEquals(constItems[constIndex], value))
|
|
--constIndex;
|
|
if (constIndex == -1)
|
|
{
|
|
Constants.PushSlot(value);
|
|
ConstantUsage.PushSlot(1);
|
|
}
|
|
else
|
|
{
|
|
++ConstantUsage.Items[constIndex];
|
|
}
|
|
}
|
|
|
|
public void AddNonPassedParam(ParameterExpression expr)
|
|
{
|
|
Status |= ClosureStatus.HasClosure;
|
|
|
|
if (NonPassedParameters.Length == 0)
|
|
{
|
|
NonPassedParameters = new[] { expr };
|
|
return;
|
|
}
|
|
|
|
var count = NonPassedParameters.Length;
|
|
for (var i = 0; i < count; ++i)
|
|
if (ReferenceEquals(NonPassedParameters[i], expr))
|
|
return;
|
|
|
|
if (NonPassedParameters.Length == 1)
|
|
NonPassedParameters = new[] { NonPassedParameters[0], expr };
|
|
else if (NonPassedParameters.Length == 2)
|
|
NonPassedParameters = new[] { NonPassedParameters[0], NonPassedParameters[1], expr };
|
|
else
|
|
{
|
|
var newItems = new ParameterExpression[count + 1];
|
|
Array.Copy(NonPassedParameters, 0, newItems, 0, count);
|
|
newItems[count] = expr;
|
|
NonPassedParameters = newItems;
|
|
}
|
|
}
|
|
|
|
public void AddNestedLambda(NestedLambdaInfo nestedLambdaInfo)
|
|
{
|
|
Status |= ClosureStatus.HasClosure;
|
|
|
|
var nestedLambdas = NestedLambdas;
|
|
var count = nestedLambdas.Length;
|
|
if (count == 0)
|
|
NestedLambdas = new[] { nestedLambdaInfo };
|
|
else if (count == 1)
|
|
NestedLambdas = new[] { nestedLambdas[0], nestedLambdaInfo };
|
|
else if (count == 2)
|
|
NestedLambdas = new[] { nestedLambdas[0], nestedLambdas[1], nestedLambdaInfo };
|
|
else
|
|
{
|
|
var newNestedLambdas = new NestedLambdaInfo[count + 1];
|
|
Array.Copy(nestedLambdas, 0, newNestedLambdas, 0, count);
|
|
newNestedLambdas[count] = nestedLambdaInfo;
|
|
NestedLambdas = newNestedLambdas;
|
|
}
|
|
}
|
|
|
|
public void AddLabel(LabelTarget labelTarget)
|
|
{
|
|
if (labelTarget != null &&
|
|
GetLabelIndex(labelTarget) == -1)
|
|
_labels = _labels.WithLast(new KeyValuePair<LabelTarget, Label?>(labelTarget, null));
|
|
}
|
|
|
|
public Label GetOrCreateLabel(LabelTarget labelTarget, ILGenerator il) =>
|
|
GetOrCreateLabel(GetLabelIndex(labelTarget), il);
|
|
|
|
public Label GetOrCreateLabel(int index, ILGenerator il)
|
|
{
|
|
var labelPair = _labels[index];
|
|
var label = labelPair.Value;
|
|
if (!label.HasValue)
|
|
_labels[index] = new KeyValuePair<LabelTarget, Label?>(labelPair.Key, label = il.DefineLabel());
|
|
return label.Value;
|
|
}
|
|
|
|
public int GetLabelIndex(LabelTarget labelTarget)
|
|
{
|
|
if (_labels != null)
|
|
for (var i = 0; i < _labels.Length; ++i)
|
|
if (_labels[i].Key == labelTarget)
|
|
return i;
|
|
return -1;
|
|
}
|
|
|
|
public void AddTryCatchFinallyInfo()
|
|
{
|
|
++CurrentTryCatchFinallyIndex;
|
|
var infos = _tryCatchFinallyInfos;
|
|
if (infos == null)
|
|
_tryCatchFinallyInfos = new int[1];
|
|
else if (infos.Length == 1)
|
|
_tryCatchFinallyInfos = new[] { infos[0], 0 };
|
|
else if (infos.Length == 2)
|
|
_tryCatchFinallyInfos = new[] { infos[0], infos[1], 0 };
|
|
else
|
|
{
|
|
var sourceLength = infos.Length;
|
|
var newInfos = new int[sourceLength + 1];
|
|
Array.Copy(infos, newInfos, sourceLength);
|
|
_tryCatchFinallyInfos = newInfos;
|
|
}
|
|
}
|
|
|
|
public void MarkAsContainsReturnGotoExpression()
|
|
{
|
|
if (CurrentTryCatchFinallyIndex != -1)
|
|
_tryCatchFinallyInfos[CurrentTryCatchFinallyIndex] |= 1;
|
|
}
|
|
|
|
public void MarkReturnLabelIndex(int index)
|
|
{
|
|
if (CurrentTryCatchFinallyIndex != -1)
|
|
_tryCatchFinallyInfos[CurrentTryCatchFinallyIndex] |= index << 1;
|
|
}
|
|
|
|
public bool TryCatchFinallyContainsReturnGotoExpression() =>
|
|
_tryCatchFinallyInfos != null && (_tryCatchFinallyInfos[++CurrentTryCatchFinallyIndex] & 1) != 0;
|
|
|
|
public object[] GetArrayOfConstantsAndNestedLambdas()
|
|
{
|
|
var constCount = Constants.Count;
|
|
var nestedLambdas = NestedLambdas;
|
|
if (constCount == 0)
|
|
{
|
|
if (nestedLambdas.Length == 0)
|
|
return null;
|
|
|
|
var nestedLambdaItems = new object[nestedLambdas.Length];
|
|
for (var i = 0; i < nestedLambdas.Length; i++)
|
|
{
|
|
var nestedLambda = nestedLambdas[i];
|
|
if (nestedLambda.ClosureInfo.NonPassedParameters.Length == 0)
|
|
nestedLambdaItems[i] = nestedLambda.Lambda;
|
|
else
|
|
nestedLambdaItems[i] = new NestedLambdaWithConstantsAndNestedLambdas(
|
|
nestedLambda.Lambda, nestedLambda.ClosureInfo.GetArrayOfConstantsAndNestedLambdas());
|
|
}
|
|
|
|
return nestedLambdaItems;
|
|
}
|
|
|
|
var constItems = Constants.Items;
|
|
if (nestedLambdas.Length == 0)
|
|
return constItems;
|
|
|
|
var itemCount = constCount + nestedLambdas.Length;
|
|
|
|
var closureItems = constItems;
|
|
if (itemCount > constItems.Length)
|
|
{
|
|
closureItems = new object[itemCount];
|
|
for (var i = 0; i < constCount; ++i)
|
|
closureItems[i] = constItems[i];
|
|
}
|
|
|
|
for (var i = 0; i < nestedLambdas.Length; i++)
|
|
{
|
|
var nestedLambda = nestedLambdas[i];
|
|
if (nestedLambda.ClosureInfo.NonPassedParameters.Length == 0)
|
|
closureItems[constCount + i] = nestedLambda.Lambda;
|
|
else
|
|
closureItems[constCount + i] = new NestedLambdaWithConstantsAndNestedLambdas(
|
|
nestedLambda.Lambda, nestedLambda.ClosureInfo.GetArrayOfConstantsAndNestedLambdas());
|
|
}
|
|
|
|
return closureItems;
|
|
}
|
|
|
|
/// LocalVar maybe a `null` in collecting phase when we only need to decide if ParameterExpression is an actual parameter or variable
|
|
public void PushBlockWithVars(ParameterExpression blockVarExpr)
|
|
{
|
|
ref var block = ref _blockStack.PushSlot();
|
|
block.VarExprs = blockVarExpr;
|
|
}
|
|
|
|
public void PushBlockWithVars(ParameterExpression blockVarExpr, int varIndex)
|
|
{
|
|
ref var block = ref _blockStack.PushSlot();
|
|
block.VarExprs = blockVarExpr;
|
|
block.VarIndexes = new[] { varIndex };
|
|
}
|
|
|
|
/// LocalVars maybe a `null` in collecting phase when we only need to decide if ParameterExpression is an actual parameter or variable
|
|
public void PushBlockWithVars(IReadOnlyList<ParameterExpression> blockVarExprs, int[] localVarIndexes = null)
|
|
{
|
|
ref var block = ref _blockStack.PushSlot();
|
|
block.VarExprs = blockVarExprs;
|
|
block.VarIndexes = localVarIndexes;
|
|
}
|
|
|
|
public void PushBlockAndConstructLocalVars(IReadOnlyList<ParameterExpression> blockVarExprs, ILGenerator il)
|
|
{
|
|
var localVars = new int[blockVarExprs.Count];
|
|
for (var i = 0; i < localVars.Length; i++)
|
|
localVars[i] = il.GetNextLocalVarIndex(blockVarExprs[i].Type);
|
|
|
|
PushBlockWithVars(blockVarExprs, localVars);
|
|
}
|
|
|
|
public void PopBlock() => _blockStack.Pop();
|
|
|
|
public bool IsLocalVar(object varParamExpr)
|
|
{
|
|
for (var i = _blockStack.Count - 1; i > -1; --i)
|
|
{
|
|
var varExprObj = _blockStack.Items[i].VarExprs;
|
|
if (ReferenceEquals(varExprObj, varParamExpr))
|
|
return true;
|
|
|
|
if (varExprObj is IReadOnlyList<ParameterExpression> varExprs)
|
|
for (var j = 0; j < varExprs.Count; j++)
|
|
if (ReferenceEquals(varExprs[j], varParamExpr))
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
public int GetDefinedLocalVarOrDefault(ParameterExpression varParamExpr)
|
|
{
|
|
for (var i = _blockStack.Count - 1; i > -1; --i)
|
|
{
|
|
ref var block = ref _blockStack.Items[i];
|
|
var varExprObj = block.VarExprs;
|
|
|
|
if (ReferenceEquals(varExprObj, varParamExpr))
|
|
return block.VarIndexes[0];
|
|
|
|
if (varExprObj is IReadOnlyList<ParameterExpression> varExprs)
|
|
for (var j = 0; j < varExprs.Count; j++)
|
|
if (ReferenceEquals(varExprs[j], varParamExpr))
|
|
return block.VarIndexes[j];
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
public bool IsTryReturnLabel(int index)
|
|
{
|
|
var tryCatchFinallyInfos = _tryCatchFinallyInfos;
|
|
if (tryCatchFinallyInfos != null)
|
|
for (var i = 0; i < tryCatchFinallyInfos.Length; ++i)
|
|
if (tryCatchFinallyInfos[i] >> 1 == index)
|
|
return true;
|
|
return false;
|
|
}
|
|
}
|
|
|
|
#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
|
|
|
|
public static readonly ArrayClosure EmptyArrayClosure = new ArrayClosure(null);
|
|
|
|
public static FieldInfo ArrayClosureArrayField =
|
|
typeof(ArrayClosure).GetTypeInfo().GetDeclaredField(nameof(ArrayClosure.ConstantsAndNestedLambdas));
|
|
|
|
public static FieldInfo ArrayClosureWithNonPassedParamsField =
|
|
typeof(ArrayClosureWithNonPassedParams).GetTypeInfo().GetDeclaredField(nameof(ArrayClosureWithNonPassedParams.NonPassedParams));
|
|
|
|
public static ConstructorInfo ArrayClosureWithNonPassedParamsConstructor =
|
|
typeof(ArrayClosureWithNonPassedParams).GetTypeInfo().DeclaredConstructors.GetFirst();
|
|
|
|
public class ArrayClosure
|
|
{
|
|
public readonly object[] ConstantsAndNestedLambdas;
|
|
public ArrayClosure(object[] constantsAndNestedLambdas) => ConstantsAndNestedLambdas = constantsAndNestedLambdas;
|
|
}
|
|
|
|
public sealed class ArrayClosureWithNonPassedParams : ArrayClosure
|
|
{
|
|
public readonly object[] NonPassedParams;
|
|
|
|
public ArrayClosureWithNonPassedParams(object[] constantsAndNestedLambdas, object[] nonPassedParams) : base(constantsAndNestedLambdas) =>
|
|
NonPassedParams = nonPassedParams;
|
|
}
|
|
|
|
public sealed class NestedLambdaWithConstantsAndNestedLambdas
|
|
{
|
|
public static FieldInfo NestedLambdaField =
|
|
typeof(NestedLambdaWithConstantsAndNestedLambdas).GetTypeInfo().GetDeclaredField(nameof(NestedLambda));
|
|
|
|
public static FieldInfo ConstantsAndNestedLambdasField =
|
|
typeof(NestedLambdaWithConstantsAndNestedLambdas).GetTypeInfo().GetDeclaredField(nameof(ConstantsAndNestedLambdas));
|
|
|
|
public readonly object NestedLambda;
|
|
public readonly object ConstantsAndNestedLambdas;
|
|
public NestedLambdaWithConstantsAndNestedLambdas(object nestedLambda, object constantsAndNestedLambdas)
|
|
{
|
|
NestedLambda = nestedLambda;
|
|
ConstantsAndNestedLambdas = constantsAndNestedLambdas;
|
|
}
|
|
}
|
|
|
|
private sealed class NestedLambdaInfo
|
|
{
|
|
public readonly LambdaExpression LambdaExpression;
|
|
public ClosureInfo ClosureInfo;
|
|
public object Lambda;
|
|
public int UsageCountOrVarIndex;
|
|
|
|
public NestedLambdaInfo(LambdaExpression lambdaExpression)
|
|
{
|
|
LambdaExpression = lambdaExpression;
|
|
ClosureInfo = new ClosureInfo(ClosureStatus.ToBeCollected);
|
|
Lambda = null;
|
|
}
|
|
}
|
|
|
|
internal static class CurryClosureFuncs
|
|
{
|
|
public static readonly MethodInfo[] Methods =
|
|
typeof(CurryClosureFuncs).GetTypeInfo().DeclaredMethods.AsArray();
|
|
|
|
public static Func<R> Curry<C, R>(Func<C, R> f, C c) =>
|
|
() => f(c);
|
|
|
|
public static Func<T1, R> Curry<C, T1, R>(Func<C, T1, R> f, C c) =>
|
|
t1 => f(c, t1);
|
|
|
|
public static Func<T1, T2, R> Curry<C, T1, T2, R>(Func<C, T1, T2, R> f, C c) =>
|
|
(t1, t2) => f(c, t1, t2);
|
|
|
|
public static Func<T1, T2, T3, R> Curry<C, T1, T2, T3, R>(Func<C, T1, T2, T3, R> f, C c) =>
|
|
(t1, t2, t3) => f(c, t1, t2, t3);
|
|
|
|
public static Func<T1, T2, T3, T4, R> Curry<C, T1, T2, T3, T4, R>(Func<C, T1, T2, T3, T4, R> f, C c) =>
|
|
(t1, t2, t3, t4) => f(c, t1, t2, t3, t4);
|
|
|
|
public static Func<T1, T2, T3, T4, T5, R> Curry<C, T1, T2, T3, T4, T5, R>(Func<C, T1, T2, T3, T4, T5, R> f,
|
|
C c) => (t1, t2, t3, t4, t5) => f(c, t1, t2, t3, t4, t5);
|
|
|
|
public static Func<T1, T2, T3, T4, T5, T6, R>
|
|
Curry<C, T1, T2, T3, T4, T5, T6, R>(Func<C, T1, T2, T3, T4, T5, T6, R> f, C c) =>
|
|
(t1, t2, t3, t4, t5, t6) => f(c, t1, t2, t3, t4, t5, t6);
|
|
}
|
|
|
|
internal static class CurryClosureActions
|
|
{
|
|
public static readonly MethodInfo[] Methods =
|
|
typeof(CurryClosureActions).GetTypeInfo().DeclaredMethods.AsArray();
|
|
|
|
public static Action Curry<C>(Action<C> a, C c) =>
|
|
() => a(c);
|
|
|
|
public static Action<T1> Curry<C, T1>(Action<C, T1> f, C c) =>
|
|
t1 => f(c, t1);
|
|
|
|
public static Action<T1, T2> Curry<C, T1, T2>(Action<C, T1, T2> f, C c) =>
|
|
(t1, t2) => f(c, t1, t2);
|
|
|
|
public static Action<T1, T2, T3> Curry<C, T1, T2, T3>(Action<C, T1, T2, T3> f, C c) =>
|
|
(t1, t2, t3) => f(c, t1, t2, t3);
|
|
|
|
public static Action<T1, T2, T3, T4> Curry<C, T1, T2, T3, T4>(Action<C, T1, T2, T3, T4> f, C c) =>
|
|
(t1, t2, t3, t4) => f(c, t1, t2, t3, t4);
|
|
|
|
public static Action<T1, T2, T3, T4, T5> Curry<C, T1, T2, T3, T4, T5>(Action<C, T1, T2, T3, T4, T5> f,
|
|
C c) => (t1, t2, t3, t4, t5) => f(c, t1, t2, t3, t4, t5);
|
|
|
|
public static Action<T1, T2, T3, T4, T5, T6>
|
|
Curry<C, T1, T2, T3, T4, T5, T6>(Action<C, T1, T2, T3, T4, T5, T6> f, C c) =>
|
|
(t1, t2, t3, t4, t5, t6) => f(c, t1, t2, t3, t4, t5, t6);
|
|
}
|
|
|
|
#region Collect Bound Constants
|
|
|
|
/// Helps to identify constants as the one to be put into the Closure
|
|
public static bool IsClosureBoundConstant(object value, TypeInfo type) =>
|
|
value is Delegate ||
|
|
!type.IsPrimitive && !type.IsEnum && value is string == false && value is Type == false && value is decimal == false;
|
|
|
|
// @paramExprs is required for nested lambda compilation
|
|
private static bool TryCollectBoundConstants(ref ClosureInfo closure, Expression expr,
|
|
IReadOnlyList<ParameterExpression> paramExprs, bool isNestedLambda, ref ClosureInfo rootClosure)
|
|
{
|
|
while (true)
|
|
{
|
|
if (expr == null)
|
|
return false;
|
|
|
|
switch (expr.NodeType)
|
|
{
|
|
case ExpressionType.Constant:
|
|
var constantExpr = (ConstantExpression)expr;
|
|
var value = constantExpr.Value;
|
|
if (value != null && IsClosureBoundConstant(value, value.GetType().GetTypeInfo()))
|
|
closure.AddConstant(value);
|
|
return true;
|
|
|
|
case ExpressionType.Quote:
|
|
//var operand = ((UnaryExpression)expr).Operand;
|
|
//if (operand != null && IsClosureBoundConstant(operand, expr.Type.GetTypeInfo()))
|
|
// closure.AddConstant(operand);
|
|
return false;
|
|
|
|
case ExpressionType.Parameter:
|
|
// if parameter is used BUT is not in passed parameters and not in local variables,
|
|
// it means parameter is provided by outer lambda and should be put in closure for current lambda
|
|
var p = paramExprs.Count - 1;
|
|
while (p != -1 && !ReferenceEquals(paramExprs[p], expr)) --p;
|
|
if (p == -1 && !closure.IsLocalVar(expr))
|
|
{
|
|
if (!isNestedLambda)
|
|
return false;
|
|
closure.AddNonPassedParam((ParameterExpression)expr);
|
|
}
|
|
return true;
|
|
|
|
case ExpressionType.Call:
|
|
var callExpr = (MethodCallExpression)expr;
|
|
var callObjectExpr = callExpr.Object;
|
|
|
|
var fewCallArgCount = callExpr.FewArgumentCount;
|
|
if (fewCallArgCount == 0)
|
|
{
|
|
if (callObjectExpr != null)
|
|
{
|
|
expr = callObjectExpr;
|
|
continue;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
if (fewCallArgCount > 0)
|
|
{
|
|
if (callObjectExpr != null &&
|
|
!TryCollectBoundConstants(ref closure, callObjectExpr, paramExprs, isNestedLambda, ref rootClosure))
|
|
return false;
|
|
|
|
if (fewCallArgCount == 1)
|
|
{
|
|
expr = ((OneArgumentMethodCallExpression)callExpr).Argument;
|
|
continue;
|
|
}
|
|
|
|
if (fewCallArgCount == 2)
|
|
{
|
|
var twoArgsExpr = (TwoArgumentsMethodCallExpression)callExpr;
|
|
if (!TryCollectBoundConstants(ref closure, twoArgsExpr.Argument0, paramExprs, isNestedLambda, ref rootClosure))
|
|
return false;
|
|
expr = twoArgsExpr.Argument1;
|
|
continue;
|
|
}
|
|
|
|
if (fewCallArgCount == 3)
|
|
{
|
|
var threeArgsExpr = (ThreeArgumentsMethodCallExpression)callExpr;
|
|
if (!TryCollectBoundConstants(ref closure, threeArgsExpr.Argument0, paramExprs, isNestedLambda, ref rootClosure) ||
|
|
!TryCollectBoundConstants(ref closure, threeArgsExpr.Argument1, paramExprs, isNestedLambda, ref rootClosure))
|
|
return false;
|
|
expr = threeArgsExpr.Argument2;
|
|
continue;
|
|
}
|
|
|
|
if (fewCallArgCount == 4)
|
|
{
|
|
var fourArgsExpr = (FourArgumentsMethodCallExpression)callExpr;
|
|
if (!TryCollectBoundConstants(ref closure, fourArgsExpr.Argument0, paramExprs, isNestedLambda, ref rootClosure) ||
|
|
!TryCollectBoundConstants(ref closure, fourArgsExpr.Argument1, paramExprs, isNestedLambda, ref rootClosure) ||
|
|
!TryCollectBoundConstants(ref closure, fourArgsExpr.Argument2, paramExprs, isNestedLambda, ref rootClosure))
|
|
return false;
|
|
expr = fourArgsExpr.Argument3;
|
|
continue;
|
|
}
|
|
|
|
var fiveArgsExpr = (FiveArgumentsMethodCallExpression)callExpr;
|
|
if (!TryCollectBoundConstants(ref closure, fiveArgsExpr.Argument0, paramExprs, isNestedLambda, ref rootClosure) ||
|
|
!TryCollectBoundConstants(ref closure, fiveArgsExpr.Argument1, paramExprs, isNestedLambda, ref rootClosure) ||
|
|
!TryCollectBoundConstants(ref closure, fiveArgsExpr.Argument2, paramExprs, isNestedLambda, ref rootClosure) ||
|
|
!TryCollectBoundConstants(ref closure, fiveArgsExpr.Argument3, paramExprs, isNestedLambda, ref rootClosure))
|
|
return false;
|
|
expr = fiveArgsExpr.Argument4;
|
|
continue;
|
|
}
|
|
|
|
var methodArgs = callExpr.Arguments;
|
|
var methodArgCount = methodArgs.Count;
|
|
if (methodArgCount == 0)
|
|
{
|
|
if (callObjectExpr != null)
|
|
{
|
|
expr = callObjectExpr;
|
|
continue;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
if (callObjectExpr != null &&
|
|
!TryCollectBoundConstants(ref closure, callExpr.Object, paramExprs, isNestedLambda, ref rootClosure))
|
|
return false;
|
|
|
|
for (var i = 0; i < methodArgCount - 1; i++)
|
|
if (!TryCollectBoundConstants(ref closure, methodArgs[i], paramExprs, isNestedLambda, ref rootClosure))
|
|
return false;
|
|
|
|
expr = methodArgs[methodArgCount - 1];
|
|
continue;
|
|
|
|
case ExpressionType.MemberAccess:
|
|
var memberExpr = ((MemberExpression)expr).Expression;
|
|
if (memberExpr == null)
|
|
return true;
|
|
expr = memberExpr;
|
|
continue;
|
|
|
|
case ExpressionType.New:
|
|
var newExpr = (NewExpression)expr;
|
|
|
|
var fewArgCount = newExpr.FewArgumentCount;
|
|
if (fewArgCount == 0)
|
|
return true;
|
|
|
|
if (fewArgCount > 0)
|
|
{
|
|
if (fewArgCount == 1)
|
|
{
|
|
expr = ((OneArgumentNewExpression)newExpr).Argument;
|
|
continue;
|
|
}
|
|
|
|
if (fewArgCount == 2)
|
|
{
|
|
var twoArgsExpr = (TwoArgumentsNewExpression)newExpr;
|
|
if (!TryCollectBoundConstants(ref closure, twoArgsExpr.Argument0, paramExprs, isNestedLambda, ref rootClosure))
|
|
return false;
|
|
expr = twoArgsExpr.Argument1;
|
|
continue;
|
|
}
|
|
|
|
if (fewArgCount == 3)
|
|
{
|
|
var threeArgsExpr = (ThreeArgumentsNewExpression)newExpr;
|
|
if (!TryCollectBoundConstants(ref closure, threeArgsExpr.Argument0, paramExprs, isNestedLambda, ref rootClosure) ||
|
|
!TryCollectBoundConstants(ref closure, threeArgsExpr.Argument1, paramExprs, isNestedLambda, ref rootClosure))
|
|
return false;
|
|
expr = threeArgsExpr.Argument2;
|
|
continue;
|
|
}
|
|
|
|
if (fewArgCount == 4)
|
|
{
|
|
var fourArgsExpr = (FourArgumentsNewExpression)newExpr;
|
|
if (!TryCollectBoundConstants(ref closure, fourArgsExpr.Argument0, paramExprs, isNestedLambda, ref rootClosure) ||
|
|
!TryCollectBoundConstants(ref closure, fourArgsExpr.Argument1, paramExprs, isNestedLambda, ref rootClosure) ||
|
|
!TryCollectBoundConstants(ref closure, fourArgsExpr.Argument2, paramExprs, isNestedLambda, ref rootClosure))
|
|
return false;
|
|
expr = fourArgsExpr.Argument3;
|
|
continue;
|
|
}
|
|
|
|
if (fewArgCount == 4)
|
|
{
|
|
var fourArgsExpr = (FourArgumentsNewExpression)newExpr;
|
|
if (!TryCollectBoundConstants(ref closure, fourArgsExpr.Argument0, paramExprs, isNestedLambda, ref rootClosure) ||
|
|
!TryCollectBoundConstants(ref closure, fourArgsExpr.Argument1, paramExprs, isNestedLambda, ref rootClosure) ||
|
|
!TryCollectBoundConstants(ref closure, fourArgsExpr.Argument2, paramExprs, isNestedLambda, ref rootClosure))
|
|
return false;
|
|
expr = fourArgsExpr.Argument3;
|
|
continue;
|
|
}
|
|
|
|
var fiveArgsExpr = (FiveArgumentsNewExpression)newExpr;
|
|
if (!TryCollectBoundConstants(ref closure, fiveArgsExpr.Argument0, paramExprs, isNestedLambda, ref rootClosure) ||
|
|
!TryCollectBoundConstants(ref closure, fiveArgsExpr.Argument1, paramExprs, isNestedLambda, ref rootClosure) ||
|
|
!TryCollectBoundConstants(ref closure, fiveArgsExpr.Argument2, paramExprs, isNestedLambda, ref rootClosure) ||
|
|
!TryCollectBoundConstants(ref closure, fiveArgsExpr.Argument3, paramExprs, isNestedLambda, ref rootClosure))
|
|
return false;
|
|
expr = fiveArgsExpr.Argument4;
|
|
continue;
|
|
}
|
|
|
|
var ctorArgs = ((NewExpression)expr).Arguments;
|
|
var ctorLastArgIndex = ctorArgs.Count - 1;
|
|
if (ctorLastArgIndex == -1)
|
|
return true;
|
|
|
|
for (var i = 0; i < ctorLastArgIndex; i++)
|
|
if (!TryCollectBoundConstants(ref closure, ctorArgs[i], paramExprs, isNestedLambda, ref rootClosure))
|
|
return false;
|
|
expr = ctorArgs[ctorLastArgIndex];
|
|
continue;
|
|
|
|
case ExpressionType.NewArrayBounds:
|
|
case ExpressionType.NewArrayInit:
|
|
var elemExprs = ((NewArrayExpression)expr).Expressions;
|
|
var elemExprsCount = elemExprs.Count;
|
|
if (elemExprsCount == 0)
|
|
return true;
|
|
|
|
for (var i = 0; i < elemExprsCount - 1; i++)
|
|
if (!TryCollectBoundConstants(ref closure, elemExprs[i], paramExprs, isNestedLambda, ref rootClosure))
|
|
return false;
|
|
|
|
expr = elemExprs[elemExprsCount - 1];
|
|
continue;
|
|
|
|
case ExpressionType.MemberInit:
|
|
return TryCollectMemberInitExprConstants(
|
|
ref closure, (MemberInitExpression)expr, paramExprs, isNestedLambda, ref rootClosure);
|
|
|
|
case ExpressionType.Lambda:
|
|
var nestedLambdaExpr = (LambdaExpression)expr;
|
|
|
|
// Look for the already collected lambdas and if we have the same lambda, start from the root
|
|
var nestedLambdas = rootClosure.NestedLambdas;
|
|
if (nestedLambdas.Length != 0)
|
|
{
|
|
var foundLambdaInfo = FindAlreadyCollectedNestedLambdaInfo(nestedLambdas, nestedLambdaExpr, out var foundInLambdas);
|
|
if (foundLambdaInfo != null)
|
|
{
|
|
// if the lambda is not found on the same level, then add it
|
|
if (foundInLambdas == closure.NestedLambdas)
|
|
{
|
|
++foundLambdaInfo.UsageCountOrVarIndex;
|
|
}
|
|
else
|
|
{
|
|
closure.AddNestedLambda(foundLambdaInfo);
|
|
var foundLambdaNonPassedParams = foundLambdaInfo.ClosureInfo.NonPassedParameters;
|
|
if (foundLambdaNonPassedParams.Length != 0)
|
|
PropagateNonPassedParamsToOuterLambda(ref closure, paramExprs, nestedLambdaExpr.Parameters, foundLambdaNonPassedParams);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
}
|
|
|
|
var nestedLambdaInfo = new NestedLambdaInfo(nestedLambdaExpr);
|
|
if (!TryCollectBoundConstants(ref nestedLambdaInfo.ClosureInfo,
|
|
nestedLambdaExpr.Body, nestedLambdaExpr.Parameters, true, ref rootClosure))
|
|
return false;
|
|
|
|
closure.AddNestedLambda(nestedLambdaInfo);
|
|
var nestedNonPassedParams = nestedLambdaInfo.ClosureInfo.NonPassedParameters;
|
|
if (nestedNonPassedParams.Length != 0)
|
|
PropagateNonPassedParamsToOuterLambda(ref closure, paramExprs, nestedLambdaExpr.Parameters, nestedNonPassedParams);
|
|
|
|
return true;
|
|
|
|
case ExpressionType.Invoke:
|
|
var invokeExpr = (InvocationExpression)expr;
|
|
var invokeArgs = invokeExpr.Arguments;
|
|
var invokeArgsCount = invokeArgs.Count;
|
|
if (invokeArgsCount == 0)
|
|
{
|
|
// optimization #138: we inline the invoked lambda body (only for lambdas without arguments)
|
|
// therefore we skipping collecting the lambda and invocation arguments and got directly to lambda body.
|
|
// This approach is repeated in `TryEmitInvoke`
|
|
expr = (invokeExpr.Expression as LambdaExpression)?.Body ?? invokeExpr.Expression;
|
|
continue;
|
|
}
|
|
else if (!TryCollectBoundConstants(ref closure, invokeExpr.Expression, paramExprs, isNestedLambda, ref rootClosure))
|
|
return false;
|
|
|
|
for (var i = 0; i < invokeArgs.Count - 1; i++)
|
|
if (!TryCollectBoundConstants(ref closure, invokeArgs[i], paramExprs, isNestedLambda, ref rootClosure))
|
|
return false;
|
|
|
|
expr = invokeArgs[invokeArgsCount - 1];
|
|
continue;
|
|
|
|
case ExpressionType.Conditional:
|
|
var condExpr = (ConditionalExpression)expr;
|
|
if (!TryCollectBoundConstants(ref closure, condExpr.Test, paramExprs, isNestedLambda, ref rootClosure) ||
|
|
!TryCollectBoundConstants(ref closure, condExpr.IfFalse, paramExprs, isNestedLambda, ref rootClosure))
|
|
return false;
|
|
expr = condExpr.IfTrue;
|
|
continue;
|
|
|
|
case ExpressionType.Block:
|
|
|
|
if (expr is OneVariableTwoExpressionBlockExpression simpleBlock)
|
|
{
|
|
closure.PushBlockWithVars(simpleBlock.Variable);
|
|
if (!TryCollectBoundConstants(ref closure, simpleBlock.Expression1, paramExprs, isNestedLambda, ref rootClosure) ||
|
|
!TryCollectBoundConstants(ref closure, simpleBlock.Expression2, paramExprs, isNestedLambda, ref rootClosure))
|
|
return false;
|
|
closure.PopBlock();
|
|
return true;
|
|
}
|
|
|
|
var blockExpr = (BlockExpression)expr;
|
|
var blockVarExprs = blockExpr.Variables;
|
|
var blockExprs = blockExpr.Expressions;
|
|
|
|
if (blockVarExprs.Count == 0)
|
|
{
|
|
for (var i = 0; i < blockExprs.Count - 1; i++)
|
|
if (!TryCollectBoundConstants(ref closure, blockExprs[i], paramExprs, isNestedLambda, ref rootClosure))
|
|
return false;
|
|
expr = blockExprs[blockExprs.Count - 1];
|
|
continue;
|
|
}
|
|
else
|
|
{
|
|
if (blockVarExprs.Count == 1)
|
|
closure.PushBlockWithVars(blockVarExprs[0]);
|
|
else
|
|
closure.PushBlockWithVars(blockVarExprs);
|
|
|
|
for (var i = 0; i < blockExprs.Count; i++)
|
|
if (!TryCollectBoundConstants(ref closure, blockExprs[i], paramExprs, isNestedLambda, ref rootClosure))
|
|
return false;
|
|
closure.PopBlock();
|
|
}
|
|
|
|
return true;
|
|
|
|
case ExpressionType.Loop:
|
|
var loopExpr = (LoopExpression)expr;
|
|
closure.AddLabel(loopExpr.BreakLabel);
|
|
closure.AddLabel(loopExpr.ContinueLabel);
|
|
expr = loopExpr.Body;
|
|
continue;
|
|
|
|
case ExpressionType.Index:
|
|
var indexExpr = (IndexExpression)expr;
|
|
var indexArgs = indexExpr.Arguments;
|
|
for (var i = 0; i < indexArgs.Count; i++)
|
|
if (!TryCollectBoundConstants(ref closure, indexArgs[i], paramExprs, isNestedLambda, ref rootClosure))
|
|
return false;
|
|
if (indexExpr.Object == null)
|
|
return true;
|
|
expr = indexExpr.Object;
|
|
continue;
|
|
|
|
case ExpressionType.Try:
|
|
return TryCollectTryExprConstants(ref closure, (TryExpression)expr, paramExprs, isNestedLambda, ref rootClosure);
|
|
|
|
case ExpressionType.Label:
|
|
var labelExpr = (LabelExpression)expr;
|
|
var defaultValueExpr = labelExpr.DefaultValue;
|
|
closure.AddLabel(labelExpr.Target);
|
|
if (defaultValueExpr == null)
|
|
return true;
|
|
expr = defaultValueExpr;
|
|
continue;
|
|
|
|
case ExpressionType.Goto:
|
|
var gotoExpr = (GotoExpression)expr;
|
|
if (gotoExpr.Kind == GotoExpressionKind.Return)
|
|
closure.MarkAsContainsReturnGotoExpression();
|
|
|
|
if (gotoExpr.Value == null)
|
|
return true;
|
|
|
|
expr = gotoExpr.Value;
|
|
continue;
|
|
|
|
case ExpressionType.Switch:
|
|
var switchExpr = ((SwitchExpression)expr);
|
|
if (!TryCollectBoundConstants(ref closure, switchExpr.SwitchValue, paramExprs, isNestedLambda, ref rootClosure) ||
|
|
switchExpr.DefaultBody != null &&
|
|
!TryCollectBoundConstants(ref closure, switchExpr.DefaultBody, paramExprs, isNestedLambda, ref rootClosure))
|
|
return false;
|
|
var switchCases = switchExpr.Cases;
|
|
for (var i = 0; i < switchCases.Count - 1; i++)
|
|
if (!TryCollectBoundConstants(ref closure, switchCases[i].Body, paramExprs, isNestedLambda, ref rootClosure))
|
|
return false;
|
|
expr = switchCases[switchCases.Count - 1].Body;
|
|
continue;
|
|
|
|
case ExpressionType.Extension:
|
|
expr = expr.Reduce();
|
|
continue;
|
|
|
|
case ExpressionType.Default:
|
|
return true;
|
|
|
|
default:
|
|
if (expr is UnaryExpression unaryExpr)
|
|
{
|
|
expr = unaryExpr.Operand;
|
|
continue;
|
|
}
|
|
|
|
if (expr is BinaryExpression binaryExpr)
|
|
{
|
|
if (!TryCollectBoundConstants(ref closure, binaryExpr.Left, paramExprs, isNestedLambda, ref rootClosure))
|
|
return false;
|
|
expr = binaryExpr.Right;
|
|
continue;
|
|
}
|
|
|
|
if (expr is TypeBinaryExpression typeBinaryExpr)
|
|
{
|
|
expr = typeBinaryExpr.Expression;
|
|
continue;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
private static void PropagateNonPassedParamsToOuterLambda(
|
|
ref ClosureInfo closure, IReadOnlyList<ParameterExpression> paramExprs,
|
|
IReadOnlyList<ParameterExpression> nestedLambdaParamExprs, ParameterExpression[] nestedNonPassedParams)
|
|
{
|
|
// If nested non passed parameter is not matched with any outer passed parameter,
|
|
// then ensure it goes to outer non passed parameter.
|
|
// But check that having a non-passed parameter in root expression is invalid.
|
|
for (var i = 0; i < nestedNonPassedParams.Length; i++)
|
|
{
|
|
var nestedNonPassedParam = nestedNonPassedParams[i];
|
|
|
|
var isInNestedLambda = false;
|
|
if (nestedLambdaParamExprs.Count != 0)
|
|
for (var p = 0; !isInNestedLambda && p < nestedLambdaParamExprs.Count; ++p)
|
|
isInNestedLambda = ReferenceEquals(nestedLambdaParamExprs[p], nestedNonPassedParam);
|
|
|
|
var isInOuterLambda = false;
|
|
if (paramExprs.Count != 0)
|
|
for (var p = 0; !isInOuterLambda && p < paramExprs.Count; ++p)
|
|
isInOuterLambda = ReferenceEquals(paramExprs[p], nestedNonPassedParam);
|
|
|
|
if (!isInNestedLambda && !isInOuterLambda)
|
|
closure.AddNonPassedParam(nestedNonPassedParam);
|
|
}
|
|
}
|
|
|
|
private static NestedLambdaInfo FindAlreadyCollectedNestedLambdaInfo(
|
|
NestedLambdaInfo[] nestedLambdas, LambdaExpression nestedLambdaExpr, out NestedLambdaInfo[] foundInLambdas)
|
|
{
|
|
for (var i = 0; i < nestedLambdas.Length; i++)
|
|
{
|
|
var lambdaInfo = nestedLambdas[i];
|
|
if (ReferenceEquals(lambdaInfo.LambdaExpression, nestedLambdaExpr))
|
|
{
|
|
foundInLambdas = nestedLambdas;
|
|
return lambdaInfo;
|
|
}
|
|
|
|
var deeperNestedLambdas = lambdaInfo.ClosureInfo.NestedLambdas;
|
|
if (deeperNestedLambdas.Length != 0)
|
|
{
|
|
var deeperLambdaInfo = FindAlreadyCollectedNestedLambdaInfo(deeperNestedLambdas, nestedLambdaExpr, out foundInLambdas);
|
|
if (deeperLambdaInfo != null)
|
|
return deeperLambdaInfo;
|
|
}
|
|
}
|
|
|
|
foundInLambdas = null;
|
|
return null;
|
|
}
|
|
|
|
private static bool TryCompileNestedLambda(ref ClosureInfo outerClosureInfo, int nestedLambdaIndex)
|
|
{
|
|
// 1. Try to compile nested lambda in place
|
|
// 2. Check that parameters used in compiled lambda are passed or closed by outer lambda
|
|
// 3. Add the compiled lambda to closure of outer lambda for later invocation
|
|
var nestedLambdaInfo = outerClosureInfo.NestedLambdas[nestedLambdaIndex];
|
|
if (nestedLambdaInfo.Lambda != null)
|
|
return true;
|
|
|
|
var nestedLambdaExpr = nestedLambdaInfo.LambdaExpression;
|
|
ref var nestedLambdaClosureInfo = ref nestedLambdaInfo.ClosureInfo;
|
|
|
|
var nestedLambdaParamExprs = nestedLambdaExpr.Parameters;
|
|
var nestedLambdaNestedLambdas = nestedLambdaClosureInfo.NestedLambdas;
|
|
if (nestedLambdaNestedLambdas.Length != 0)
|
|
for (var i = 0; i < nestedLambdaNestedLambdas.Length; ++i)
|
|
if (!TryCompileNestedLambda(ref nestedLambdaClosureInfo, i))
|
|
return false;
|
|
|
|
ArrayClosure nestedLambdaClosure = null;
|
|
if (nestedLambdaClosureInfo.NonPassedParameters.Length == 0)
|
|
{
|
|
if ((nestedLambdaClosureInfo.Status & ClosureStatus.HasClosure) == 0)
|
|
nestedLambdaClosure = EmptyArrayClosure;
|
|
else
|
|
nestedLambdaClosure = new ArrayClosure(nestedLambdaClosureInfo.GetArrayOfConstantsAndNestedLambdas());
|
|
}
|
|
|
|
var nestedReturnType = nestedLambdaExpr.ReturnType;
|
|
var closurePlusParamTypes = GetClosureTypeToParamTypes(nestedLambdaParamExprs);
|
|
|
|
var method = new DynamicMethod(string.Empty,
|
|
nestedReturnType, closurePlusParamTypes, typeof(ArrayClosure), true);
|
|
|
|
var il = method.GetILGenerator();
|
|
|
|
if ((nestedLambdaClosureInfo.Status & ClosureStatus.HasClosure) != 0)
|
|
EmittingVisitor.EmitLoadConstantsAndNestedLambdasIntoVars(il, ref nestedLambdaClosureInfo);
|
|
|
|
var parentFlags = nestedReturnType == typeof(void) ? ParentFlags.IgnoreResult : ParentFlags.Empty;
|
|
if (!EmittingVisitor.TryEmit(nestedLambdaExpr.Body, nestedLambdaParamExprs, il, ref nestedLambdaClosureInfo, parentFlags))
|
|
return false;
|
|
il.Emit(OpCodes.Ret);
|
|
|
|
if (nestedLambdaClosure != null)
|
|
{
|
|
nestedLambdaInfo.Lambda = method.CreateDelegate(nestedLambdaExpr.Type, nestedLambdaClosure);
|
|
}
|
|
else
|
|
{
|
|
// Otherwise create a static or an open delegate to pass closure later with `TryEmitNestedLambda`,
|
|
// constructing the new closure with non-passed arguments and the rest of items
|
|
nestedLambdaInfo.Lambda = method.CreateDelegate(
|
|
Tools.GetFuncOrActionType(closurePlusParamTypes, nestedReturnType),
|
|
null);
|
|
}
|
|
|
|
ReturnClosureTypeToParamTypesToPool(closurePlusParamTypes);
|
|
return true;
|
|
}
|
|
|
|
private static bool TryCollectMemberInitExprConstants(ref ClosureInfo closure, MemberInitExpression expr,
|
|
IReadOnlyList<ParameterExpression> paramExprs, bool isNestedLambda, ref ClosureInfo rootClosure)
|
|
{
|
|
var newExpr = expr.NewExpression
|
|
?? expr.Expression
|
|
;
|
|
if (!TryCollectBoundConstants(ref closure, newExpr, paramExprs, isNestedLambda, ref rootClosure))
|
|
return false;
|
|
|
|
var memberBindings = expr.Bindings;
|
|
for (var i = 0; i < memberBindings.Count; ++i)
|
|
{
|
|
var memberBinding = memberBindings[i];
|
|
if (memberBinding.BindingType == MemberBindingType.Assignment &&
|
|
!TryCollectBoundConstants(
|
|
ref closure, ((MemberAssignment)memberBinding).Expression, paramExprs, isNestedLambda, ref rootClosure))
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
private static bool TryCollectTryExprConstants(ref ClosureInfo closure, TryExpression tryExpr,
|
|
IReadOnlyList<ParameterExpression> paramExprs, bool isNestedLambda, ref ClosureInfo rootClosure)
|
|
{
|
|
closure.AddTryCatchFinallyInfo();
|
|
|
|
if (!TryCollectBoundConstants(ref closure, tryExpr.Body, paramExprs, isNestedLambda, ref rootClosure))
|
|
return false;
|
|
|
|
var catchBlocks = tryExpr.Handlers;
|
|
for (var i = 0; i < catchBlocks.Count; i++)
|
|
{
|
|
var catchBlock = catchBlocks[i];
|
|
var catchExVar = catchBlock.Variable;
|
|
if (catchExVar != null)
|
|
{
|
|
closure.PushBlockWithVars(catchExVar);
|
|
if (!TryCollectBoundConstants(ref closure, catchExVar, paramExprs, isNestedLambda, ref rootClosure))
|
|
return false;
|
|
}
|
|
|
|
if (catchBlock.Filter != null &&
|
|
!TryCollectBoundConstants(ref closure, catchBlock.Filter, paramExprs, isNestedLambda, ref rootClosure))
|
|
return false;
|
|
|
|
if (!TryCollectBoundConstants(ref closure, catchBlock.Body, paramExprs, isNestedLambda, ref rootClosure))
|
|
return false;
|
|
|
|
if (catchExVar != null)
|
|
closure.PopBlock();
|
|
}
|
|
|
|
if (tryExpr.Finally != null &&
|
|
!TryCollectBoundConstants(ref closure, tryExpr.Finally, paramExprs, isNestedLambda, ref rootClosure))
|
|
return false;
|
|
|
|
--closure.CurrentTryCatchFinallyIndex;
|
|
return true;
|
|
}
|
|
|
|
#endregion
|
|
|
|
// The minimal context-aware flags set by parent
|
|
[Flags]
|
|
internal enum ParentFlags
|
|
{
|
|
Empty = 0,
|
|
IgnoreResult = 1 << 1,
|
|
Call = 1 << 2,
|
|
MemberAccess = 1 << 3, // Any Parent Expression is a MemberExpression
|
|
Arithmetic = 1 << 4,
|
|
Coalesce = 1 << 5,
|
|
InstanceAccess = 1 << 6,
|
|
DupMemberOwner = 1 << 7,
|
|
TryCatch = 1 << 8,
|
|
InstanceCall = Call | InstanceAccess
|
|
}
|
|
|
|
internal static bool IgnoresResult(this ParentFlags parent) => (parent & ParentFlags.IgnoreResult) != 0;
|
|
|
|
/// <summary>Supports emitting of selected expressions, e.g. lambdaExpr are not supported yet.
|
|
/// When emitter find not supported expression it will return false from <see cref="TryEmit"/>, so I could fallback
|
|
/// to normal and slow Expression.Compile.</summary>
|
|
private static class EmittingVisitor
|
|
{
|
|
#if NETSTANDARD1_1 || NETSTANDARD1_2 || NETSTANDARD1_3 || NETSTANDARD1_4 || NETSTANDARD1_5 || NETSTANDARD1_6
|
|
private static readonly MethodInfo _getTypeFromHandleMethod =
|
|
typeof(Type).GetTypeInfo().GetDeclaredMethod("GetTypeFromHandle");
|
|
|
|
private static readonly MethodInfo _objectEqualsMethod = GetObjectEquals();
|
|
private static MethodInfo GetObjectEquals()
|
|
{
|
|
var ms = typeof(object).GetTypeInfo().GetDeclaredMethods("Equals");
|
|
foreach (var m in ms)
|
|
if (m.GetParameters().Length == 2)
|
|
return m;
|
|
throw new InvalidOperationException("object.Equals is not found");
|
|
}
|
|
#else
|
|
private static readonly MethodInfo _getTypeFromHandleMethod =
|
|
((Func<RuntimeTypeHandle, Type>)Type.GetTypeFromHandle).Method;
|
|
|
|
private static readonly MethodInfo _objectEqualsMethod =
|
|
((Func<object, object, bool>)object.Equals).Method;
|
|
#endif
|
|
|
|
public static bool TryEmit(Expression expr, IReadOnlyList<ParameterExpression> paramExprs,
|
|
ILGenerator il, ref ClosureInfo closure, ParentFlags parent, int byRefIndex = -1)
|
|
{
|
|
while (true)
|
|
{
|
|
closure.LastEmitIsAddress = false;
|
|
|
|
switch (expr.NodeType)
|
|
{
|
|
case ExpressionType.Parameter:
|
|
return (parent & ParentFlags.IgnoreResult) != 0 ||
|
|
TryEmitParameter((ParameterExpression)expr, paramExprs, il, ref closure, parent, byRefIndex);
|
|
|
|
case ExpressionType.TypeAs:
|
|
case ExpressionType.IsTrue:
|
|
case ExpressionType.IsFalse:
|
|
case ExpressionType.Increment:
|
|
case ExpressionType.Decrement:
|
|
case ExpressionType.Negate:
|
|
case ExpressionType.NegateChecked:
|
|
case ExpressionType.OnesComplement:
|
|
case ExpressionType.UnaryPlus:
|
|
case ExpressionType.Unbox:
|
|
return TryEmitSimpleUnaryExpression((UnaryExpression)expr, paramExprs, il, ref closure, parent);
|
|
|
|
case ExpressionType.Quote:
|
|
//return TryEmitNotNullConstant(true, expr.Type, ((UnaryExpression)expr).Operand, il, ref closure);
|
|
return false;
|
|
|
|
case ExpressionType.TypeIs:
|
|
return TryEmitTypeIs((TypeBinaryExpression)expr, paramExprs, il, ref closure, parent);
|
|
|
|
case ExpressionType.Not:
|
|
return TryEmitNot((UnaryExpression)expr, paramExprs, il, ref closure, parent);
|
|
|
|
case ExpressionType.Convert:
|
|
case ExpressionType.ConvertChecked:
|
|
return TryEmitConvert((UnaryExpression)expr, paramExprs, il, ref closure, parent);
|
|
|
|
case ExpressionType.ArrayIndex:
|
|
var arrIndexExpr = (BinaryExpression)expr;
|
|
return TryEmit(arrIndexExpr.Left, paramExprs, il, ref closure, parent) &&
|
|
TryEmit(arrIndexExpr.Right, paramExprs, il, ref closure, parent) &&
|
|
TryEmitArrayIndex(expr.Type, il);
|
|
|
|
case ExpressionType.ArrayLength:
|
|
var arrLengthExpr = (UnaryExpression)expr;
|
|
return TryEmitArrayLength(arrLengthExpr, paramExprs, il, ref closure, parent);
|
|
|
|
case ExpressionType.Constant:
|
|
var constExpr = (ConstantExpression)expr;
|
|
if ((parent & ParentFlags.IgnoreResult) != 0)
|
|
return true;
|
|
|
|
if (constExpr.Value == null)
|
|
{
|
|
il.Emit(OpCodes.Ldnull);
|
|
return true;
|
|
}
|
|
|
|
return TryEmitNotNullConstant(true, constExpr.Type, constExpr.Value, il, ref closure);
|
|
|
|
case ExpressionType.Call:
|
|
return TryEmitMethodCall(expr, paramExprs, il, ref closure, parent);
|
|
|
|
case ExpressionType.MemberAccess:
|
|
return TryEmitMemberAccess((MemberExpression)expr, paramExprs, il, ref closure, parent);
|
|
|
|
case ExpressionType.New:
|
|
return TryEmitNew(expr, paramExprs, il, ref closure, parent);
|
|
|
|
case ExpressionType.NewArrayBounds:
|
|
case ExpressionType.NewArrayInit:
|
|
return EmitNewArray((NewArrayExpression)expr, paramExprs, il, ref closure, parent);
|
|
|
|
case ExpressionType.MemberInit:
|
|
return EmitMemberInit((MemberInitExpression)expr, paramExprs, il, ref closure, parent);
|
|
|
|
case ExpressionType.Lambda:
|
|
return TryEmitNestedLambda((LambdaExpression)expr, paramExprs, il, ref closure);
|
|
|
|
case ExpressionType.Invoke:
|
|
// optimization #138: we inline the invoked lambda body (only for lambdas without arguments)
|
|
if (((InvocationExpression)expr).Expression is LambdaExpression lambdaExpr && lambdaExpr.Parameters.Count == 0)
|
|
{
|
|
expr = lambdaExpr.Body;
|
|
continue;
|
|
}
|
|
|
|
return TryEmitInvoke((InvocationExpression)expr, paramExprs, il, ref closure, parent);
|
|
|
|
case ExpressionType.GreaterThan:
|
|
case ExpressionType.GreaterThanOrEqual:
|
|
case ExpressionType.LessThan:
|
|
case ExpressionType.LessThanOrEqual:
|
|
case ExpressionType.Equal:
|
|
case ExpressionType.NotEqual:
|
|
var binaryExpr = (BinaryExpression)expr;
|
|
return TryEmitComparison(binaryExpr.Left, binaryExpr.Right, binaryExpr.NodeType,
|
|
paramExprs, il, ref closure, parent);
|
|
|
|
case ExpressionType.Add:
|
|
case ExpressionType.AddChecked:
|
|
case ExpressionType.Subtract:
|
|
case ExpressionType.SubtractChecked:
|
|
case ExpressionType.Multiply:
|
|
case ExpressionType.MultiplyChecked:
|
|
case ExpressionType.Divide:
|
|
case ExpressionType.Modulo:
|
|
case ExpressionType.Power:
|
|
case ExpressionType.And:
|
|
case ExpressionType.Or:
|
|
case ExpressionType.ExclusiveOr:
|
|
case ExpressionType.LeftShift:
|
|
case ExpressionType.RightShift:
|
|
var arithmeticExpr = (BinaryExpression)expr;
|
|
return TryEmitArithmetic(arithmeticExpr, expr.NodeType, paramExprs, il, ref closure, parent);
|
|
|
|
case ExpressionType.AndAlso:
|
|
case ExpressionType.OrElse:
|
|
return TryEmitLogicalOperator((BinaryExpression)expr, paramExprs, il, ref closure, parent);
|
|
|
|
case ExpressionType.Coalesce:
|
|
return TryEmitCoalesceOperator((BinaryExpression)expr, paramExprs, il, ref closure, parent);
|
|
|
|
case ExpressionType.Conditional:
|
|
return TryEmitConditional((ConditionalExpression)expr, paramExprs, il, ref closure, parent);
|
|
|
|
case ExpressionType.PostIncrementAssign:
|
|
case ExpressionType.PreIncrementAssign:
|
|
case ExpressionType.PostDecrementAssign:
|
|
case ExpressionType.PreDecrementAssign:
|
|
return TryEmitIncDecAssign((UnaryExpression)expr, paramExprs, il, ref closure, parent);
|
|
|
|
case ExpressionType.AddAssign:
|
|
case ExpressionType.AddAssignChecked:
|
|
case ExpressionType.SubtractAssign:
|
|
case ExpressionType.SubtractAssignChecked:
|
|
case ExpressionType.MultiplyAssign:
|
|
case ExpressionType.MultiplyAssignChecked:
|
|
case ExpressionType.DivideAssign:
|
|
case ExpressionType.ModuloAssign:
|
|
case ExpressionType.PowerAssign:
|
|
case ExpressionType.AndAssign:
|
|
case ExpressionType.OrAssign:
|
|
case ExpressionType.ExclusiveOrAssign:
|
|
case ExpressionType.LeftShiftAssign:
|
|
case ExpressionType.RightShiftAssign:
|
|
case ExpressionType.Assign:
|
|
return TryEmitAssign((BinaryExpression)expr, paramExprs, il, ref closure, parent);
|
|
|
|
case ExpressionType.Block:
|
|
if (expr is OneVariableTwoExpressionBlockExpression simpleBlockExpr)
|
|
{
|
|
closure.PushBlockWithVars(simpleBlockExpr.Variable, il.GetNextLocalVarIndex(simpleBlockExpr.Variable.Type));
|
|
if (!TryEmit(simpleBlockExpr.Expression1, paramExprs, il, ref closure, parent | ParentFlags.IgnoreResult) ||
|
|
!TryEmit(simpleBlockExpr.Expression2, paramExprs, il, ref closure, parent))
|
|
return false;
|
|
closure.PopBlock();
|
|
return true;
|
|
}
|
|
|
|
var blockExpr = (BlockExpression)expr;
|
|
var blockVarExprs = blockExpr.Variables;
|
|
var blockVarCount = blockVarExprs.Count;
|
|
if (blockVarCount == 1)
|
|
closure.PushBlockWithVars(blockVarExprs[0], il.GetNextLocalVarIndex(blockVarExprs[0].Type));
|
|
else if (blockVarCount > 1)
|
|
closure.PushBlockAndConstructLocalVars(blockVarExprs, il);
|
|
|
|
var statementExprs = blockExpr.Expressions; // Trim the expressions after the Throw - #196
|
|
var statementCount = statementExprs.Count;
|
|
expr = statementExprs[statementCount - 1]; // The last (result) statement in block will provide the result
|
|
|
|
// Try to trim the statements up to the Throw (if any)
|
|
if (statementCount > 1)
|
|
{
|
|
var throwIndex = statementCount - 1;
|
|
while (throwIndex != -1 && statementExprs[throwIndex].NodeType != ExpressionType.Throw)
|
|
--throwIndex;
|
|
|
|
// If we have a Throw and it is not the last one
|
|
if (throwIndex != -1 && throwIndex != statementCount - 1)
|
|
{
|
|
// Change the Throw return type to match the one for the Block, and adjust the statement count
|
|
expr = Expression.Throw(((UnaryExpression)statementExprs[throwIndex]).Operand, blockExpr.Type);
|
|
statementCount = throwIndex + 1;
|
|
}
|
|
}
|
|
|
|
// handle the all statements in block excluding the last one
|
|
if (statementCount > 1)
|
|
for (var i = 0; i < statementCount - 1; i++)
|
|
if (!TryEmit(statementExprs[i], paramExprs, il, ref closure, parent | ParentFlags.IgnoreResult))
|
|
return false;
|
|
|
|
if (blockVarCount == 0)
|
|
continue; // OMG, no recursion, continue with last expression
|
|
|
|
if (!TryEmit(expr, paramExprs, il, ref closure, parent))
|
|
return false;
|
|
|
|
closure.PopBlock();
|
|
return true;
|
|
|
|
case ExpressionType.Loop:
|
|
return TryEmitLoop((LoopExpression)expr, paramExprs, il, ref closure, parent);
|
|
|
|
case ExpressionType.Try:
|
|
return TryEmitTryCatchFinallyBlock((TryExpression)expr, paramExprs, il, ref closure,
|
|
parent | ParentFlags.TryCatch);
|
|
|
|
case ExpressionType.Throw:
|
|
{
|
|
if (!TryEmit(((UnaryExpression)expr).Operand, paramExprs, il, ref closure, parent & ~ParentFlags.IgnoreResult))
|
|
return false;
|
|
il.Emit(OpCodes.Throw);
|
|
return true;
|
|
}
|
|
|
|
case ExpressionType.Default:
|
|
if (expr.Type != typeof(void) && (parent & ParentFlags.IgnoreResult) == 0)
|
|
EmitDefault(expr.Type, il);
|
|
return true;
|
|
|
|
case ExpressionType.Index:
|
|
return TryEmitIndex((IndexExpression)expr, paramExprs, il, ref closure, parent);
|
|
|
|
case ExpressionType.Goto:
|
|
return TryEmitGoto((GotoExpression)expr, paramExprs, il, ref closure, parent);
|
|
|
|
case ExpressionType.Label:
|
|
return TryEmitLabel((LabelExpression)expr, paramExprs, il, ref closure, parent);
|
|
|
|
case ExpressionType.Switch:
|
|
return TryEmitSwitch((SwitchExpression)expr, paramExprs, il, ref closure, parent);
|
|
|
|
case ExpressionType.Extension:
|
|
expr = expr.Reduce();
|
|
continue;
|
|
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
private static bool TryEmitNew(Expression expr, IReadOnlyList<ParameterExpression> paramExprs, ILGenerator il, ref ClosureInfo closure,
|
|
ParentFlags parent)
|
|
{
|
|
var newExpr = (NewExpression)expr;
|
|
|
|
var argCount = newExpr.FewArgumentCount;
|
|
if (argCount > 0)
|
|
{
|
|
var args = newExpr.Constructor.GetParameters();
|
|
if (argCount == 1)
|
|
{
|
|
var argExpr = ((OneArgumentNewExpression)newExpr).Argument;
|
|
if (!TryEmit(argExpr, paramExprs, il, ref closure, parent, args[0].ParameterType.IsByRef ? 0 : -1))
|
|
return false;
|
|
}
|
|
else if (argCount == 2)
|
|
{
|
|
var twoArgsExpr = (TwoArgumentsNewExpression)newExpr;
|
|
if (!TryEmit(twoArgsExpr.Argument0, paramExprs, il, ref closure, parent, args[0].ParameterType.IsByRef ? 0 : -1) ||
|
|
!TryEmit(twoArgsExpr.Argument1, paramExprs, il, ref closure, parent, args[1].ParameterType.IsByRef ? 1 : -1))
|
|
return false;
|
|
}
|
|
else if (argCount == 3)
|
|
{
|
|
var threeArgsExpr = (ThreeArgumentsNewExpression)newExpr;
|
|
if (!TryEmit(threeArgsExpr.Argument0, paramExprs, il, ref closure, parent, args[0].ParameterType.IsByRef ? 0 : -1) ||
|
|
!TryEmit(threeArgsExpr.Argument1, paramExprs, il, ref closure, parent, args[1].ParameterType.IsByRef ? 1 : -1) ||
|
|
!TryEmit(threeArgsExpr.Argument2, paramExprs, il, ref closure, parent, args[2].ParameterType.IsByRef ? 2 : -1))
|
|
return false;
|
|
}
|
|
else if (argCount == 4)
|
|
{
|
|
var fourArgsExpr = (FourArgumentsNewExpression)newExpr;
|
|
if (!TryEmit(fourArgsExpr.Argument0, paramExprs, il, ref closure, parent, args[0].ParameterType.IsByRef ? 0 : -1) ||
|
|
!TryEmit(fourArgsExpr.Argument1, paramExprs, il, ref closure, parent, args[1].ParameterType.IsByRef ? 1 : -1) ||
|
|
!TryEmit(fourArgsExpr.Argument2, paramExprs, il, ref closure, parent, args[2].ParameterType.IsByRef ? 2 : -1) ||
|
|
!TryEmit(fourArgsExpr.Argument3, paramExprs, il, ref closure, parent, args[3].ParameterType.IsByRef ? 3 : -1))
|
|
return false;
|
|
}
|
|
else if (argCount == 5)
|
|
{
|
|
var fourArgsExpr = (FiveArgumentsNewExpression)newExpr;
|
|
if (!TryEmit(fourArgsExpr.Argument0, paramExprs, il, ref closure, parent, args[0].ParameterType.IsByRef ? 0 : -1) ||
|
|
!TryEmit(fourArgsExpr.Argument1, paramExprs, il, ref closure, parent, args[1].ParameterType.IsByRef ? 1 : -1) ||
|
|
!TryEmit(fourArgsExpr.Argument2, paramExprs, il, ref closure, parent, args[2].ParameterType.IsByRef ? 2 : -1) ||
|
|
!TryEmit(fourArgsExpr.Argument3, paramExprs, il, ref closure, parent, args[3].ParameterType.IsByRef ? 3 : -1) ||
|
|
!TryEmit(fourArgsExpr.Argument4, paramExprs, il, ref closure, parent, args[4].ParameterType.IsByRef ? 4 : -1))
|
|
return false;
|
|
}
|
|
|
|
// ReSharper disable once ConditionIsAlwaysTrueOrFalse
|
|
if (newExpr.Constructor != null)
|
|
il.Emit(OpCodes.Newobj, newExpr.Constructor);
|
|
else if (newExpr.Type.IsValueType())
|
|
EmitLoadLocalVariable(il, InitValueTypeVariable(il, newExpr.Type));
|
|
else
|
|
return false;
|
|
return true;
|
|
}
|
|
|
|
var argExprs = newExpr.Arguments;
|
|
if (argExprs.Count != 0)
|
|
{
|
|
var args = newExpr.Constructor.GetParameters();
|
|
for (var i = 0; i < args.Length; ++i)
|
|
if (!TryEmit(argExprs[i], paramExprs, il, ref closure, parent, args[i].ParameterType.IsByRef ? i : -1))
|
|
return false;
|
|
}
|
|
|
|
// ReSharper disable once ConditionIsAlwaysTrueOrFalse
|
|
if (newExpr.Constructor != null)
|
|
il.Emit(OpCodes.Newobj, newExpr.Constructor);
|
|
else if (newExpr.Type.IsValueType())
|
|
EmitLoadLocalVariable(il, InitValueTypeVariable(il, newExpr.Type));
|
|
else
|
|
return false;
|
|
return true;
|
|
}
|
|
|
|
private static bool TryEmitArrayLength(UnaryExpression arrLengthExpr,
|
|
IReadOnlyList<ParameterExpression> paramExprs, ILGenerator il, ref ClosureInfo closure,
|
|
ParentFlags parent)
|
|
{
|
|
if (!TryEmit(arrLengthExpr.Operand, paramExprs, il, ref closure, parent))
|
|
return false;
|
|
|
|
if ((parent & ParentFlags.IgnoreResult) == 0)
|
|
{
|
|
il.Emit(OpCodes.Ldlen);
|
|
il.Emit(OpCodes.Conv_I4);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
private static bool TryEmitLoop(LoopExpression loopExpr,
|
|
IReadOnlyList<ParameterExpression> paramExprs, ILGenerator il, ref ClosureInfo closure, ParentFlags parent)
|
|
{
|
|
// Mark the start of the loop body:
|
|
var loopBodyLabel = il.DefineLabel();
|
|
il.MarkLabel(loopBodyLabel);
|
|
|
|
if (loopExpr.ContinueLabel != null)
|
|
il.MarkLabel(closure.GetOrCreateLabel(loopExpr.ContinueLabel, il));
|
|
|
|
if (!TryEmit(loopExpr.Body, paramExprs, il, ref closure, parent))
|
|
return false;
|
|
|
|
// If loop hasn't exited, jump back to start of its body:
|
|
il.Emit(OpCodes.Br, loopBodyLabel);
|
|
|
|
if (loopExpr.BreakLabel != null)
|
|
il.MarkLabel(closure.GetOrCreateLabel(loopExpr.BreakLabel, il));
|
|
|
|
return true;
|
|
}
|
|
|
|
private static bool TryEmitIndex(IndexExpression indexExpr,
|
|
IReadOnlyList<ParameterExpression> paramExprs, ILGenerator il, ref ClosureInfo closure, ParentFlags parent)
|
|
{
|
|
if (indexExpr.Object != null &&
|
|
!TryEmit(indexExpr.Object, paramExprs, il, ref closure, parent))
|
|
return false;
|
|
|
|
var indexArgExprs = indexExpr.Arguments;
|
|
for (var i = 0; i < indexArgExprs.Count; i++)
|
|
if (!TryEmit(indexArgExprs[i], paramExprs, il, ref closure, parent,
|
|
indexArgExprs[i].Type.IsByRef ? i : -1))
|
|
return false;
|
|
|
|
var indexerProp = indexExpr.Indexer;
|
|
if (indexerProp != null)
|
|
return EmitMethodCall(il, indexerProp.DeclaringType.FindPropertyGetMethod(indexerProp.Name));
|
|
|
|
if (indexExpr.Arguments.Count == 1) // one dimensional array
|
|
return TryEmitArrayIndex(indexExpr.Type, il);
|
|
|
|
// multi dimensional array
|
|
return EmitMethodCall(il, indexExpr.Object?.Type.FindMethod("Get"));
|
|
}
|
|
|
|
private static bool TryEmitLabel(LabelExpression expr,
|
|
IReadOnlyList<ParameterExpression> paramExprs, ILGenerator il, ref ClosureInfo closure, ParentFlags parent)
|
|
{
|
|
var index = closure.GetLabelIndex(expr.Target);
|
|
if (index == -1)
|
|
return false; // should be found in first collecting constants round
|
|
|
|
if (closure.IsTryReturnLabel(index))
|
|
return true; // label will be emitted by TryEmitTryCatchFinallyBlock
|
|
|
|
// define a new label or use the label provided by the preceding GoTo expression
|
|
var label = closure.GetOrCreateLabel(index, il);
|
|
|
|
il.MarkLabel(label);
|
|
|
|
return expr.DefaultValue == null || TryEmit(expr.DefaultValue, paramExprs, il, ref closure, parent);
|
|
}
|
|
|
|
private static bool TryEmitGoto(GotoExpression expr,
|
|
IReadOnlyList<ParameterExpression> paramExprs, ILGenerator il, ref ClosureInfo closure, ParentFlags parent)
|
|
{
|
|
var index = closure.GetLabelIndex(expr.Target);
|
|
if (index == -1)
|
|
{
|
|
if ((closure.Status & ClosureStatus.ToBeCollected) == 0)
|
|
return false; // if no collection cycle then the labels may be not collected
|
|
throw new InvalidOperationException("Cannot jump, no labels found");
|
|
}
|
|
|
|
if (expr.Value != null &&
|
|
!TryEmit(expr.Value, paramExprs, il, ref closure, parent & ~ParentFlags.IgnoreResult))
|
|
return false;
|
|
|
|
switch (expr.Kind)
|
|
{
|
|
case GotoExpressionKind.Break:
|
|
case GotoExpressionKind.Continue:
|
|
// use label defined by Label expression or define its own to use by subsequent Label
|
|
il.Emit(OpCodes.Br, closure.GetOrCreateLabel(index, il));
|
|
return true;
|
|
|
|
case GotoExpressionKind.Goto:
|
|
if (expr.Value != null)
|
|
goto case GotoExpressionKind.Return;
|
|
|
|
// use label defined by Label expression or define its own to use by subsequent Label
|
|
il.Emit(OpCodes.Br, closure.GetOrCreateLabel(index, il));
|
|
return true;
|
|
|
|
case GotoExpressionKind.Return:
|
|
|
|
// check that we are inside the Try-Catch-Finally block
|
|
if ((parent & ParentFlags.TryCatch) != 0)
|
|
{
|
|
// Can't emit a Return inside a Try/Catch, so leave it to TryEmitTryCatchFinallyBlock
|
|
// to emit the Leave instruction, return label and return result
|
|
closure.MarkReturnLabelIndex(index);
|
|
}
|
|
else
|
|
{
|
|
// use label defined by Label expression or define its own to use by subsequent Label
|
|
il.Emit(OpCodes.Ret, closure.GetOrCreateLabel(index, il));
|
|
}
|
|
|
|
return true;
|
|
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
private static bool TryEmitCoalesceOperator(BinaryExpression exprObj,
|
|
IReadOnlyList<ParameterExpression> paramExprs, ILGenerator il, ref ClosureInfo closure, ParentFlags parent)
|
|
{
|
|
var labelFalse = il.DefineLabel();
|
|
var labelDone = il.DefineLabel();
|
|
|
|
var left = exprObj.Left;
|
|
var right = exprObj.Right;
|
|
|
|
if (!TryEmit(left, paramExprs, il, ref closure, parent | ParentFlags.Coalesce))
|
|
return false;
|
|
|
|
var leftType = left.Type;
|
|
if (leftType.IsValueType()) // Nullable -> It's the only ValueType comparable to null
|
|
{
|
|
var varIndex = EmitStoreLocalVariableAndLoadItsAddress(il, leftType);
|
|
il.Emit(OpCodes.Call, leftType.FindNullableHasValueGetterMethod());
|
|
|
|
il.Emit(OpCodes.Brfalse, labelFalse);
|
|
EmitLoadLocalVariableAddress(il, varIndex);
|
|
il.Emit(OpCodes.Call, leftType.FindNullableGetValueOrDefaultMethod());
|
|
|
|
il.Emit(OpCodes.Br, labelDone);
|
|
il.MarkLabel(labelFalse);
|
|
if (!TryEmit(right, paramExprs, il, ref closure, parent | ParentFlags.Coalesce))
|
|
return false;
|
|
|
|
il.MarkLabel(labelDone);
|
|
return true;
|
|
}
|
|
|
|
il.Emit(OpCodes.Dup); // duplicate left, if it's not null, after the branch this value will be on the top of the stack
|
|
il.Emit(OpCodes.Ldnull);
|
|
il.Emit(OpCodes.Ceq);
|
|
il.Emit(OpCodes.Brfalse, labelFalse);
|
|
|
|
il.Emit(OpCodes.Pop); // left is null, pop its value from the stack
|
|
|
|
if (!TryEmit(right, paramExprs, il, ref closure, parent | ParentFlags.Coalesce))
|
|
return false;
|
|
|
|
if (right.Type != exprObj.Type)
|
|
{
|
|
if (right.Type.IsValueType())
|
|
il.Emit(OpCodes.Box, right.Type);
|
|
}
|
|
|
|
if (left.Type == exprObj.Type)
|
|
il.MarkLabel(labelFalse);
|
|
else
|
|
{
|
|
il.Emit(OpCodes.Br, labelDone);
|
|
il.MarkLabel(labelFalse);
|
|
il.Emit(OpCodes.Castclass, exprObj.Type);
|
|
il.MarkLabel(labelDone);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
private static void EmitDefault(Type type, ILGenerator il)
|
|
{
|
|
if (!type.GetTypeInfo().IsValueType)
|
|
{
|
|
il.Emit(OpCodes.Ldnull);
|
|
}
|
|
else if (
|
|
type == typeof(bool) ||
|
|
type == typeof(byte) ||
|
|
type == typeof(char) ||
|
|
type == typeof(sbyte) ||
|
|
type == typeof(int) ||
|
|
type == typeof(uint) ||
|
|
type == typeof(short) ||
|
|
type == typeof(ushort))
|
|
{
|
|
il.Emit(OpCodes.Ldc_I4_0);
|
|
}
|
|
else if (
|
|
type == typeof(long) ||
|
|
type == typeof(ulong))
|
|
{
|
|
il.Emit(OpCodes.Ldc_I4_0);
|
|
il.Emit(OpCodes.Conv_I8);
|
|
}
|
|
else if (type == typeof(float))
|
|
il.Emit(OpCodes.Ldc_R4, default(float));
|
|
else if (type == typeof(double))
|
|
il.Emit(OpCodes.Ldc_R8, default(double));
|
|
else
|
|
EmitLoadLocalVariable(il, InitValueTypeVariable(il, type));
|
|
}
|
|
|
|
private static bool TryEmitTryCatchFinallyBlock(TryExpression tryExpr,
|
|
IReadOnlyList<ParameterExpression> paramExprs, ILGenerator il, ref ClosureInfo closure, ParentFlags parent)
|
|
{
|
|
var containsReturnGotoExpression = closure.TryCatchFinallyContainsReturnGotoExpression();
|
|
il.BeginExceptionBlock();
|
|
|
|
if (!TryEmit(tryExpr.Body, paramExprs, il, ref closure, parent))
|
|
return false;
|
|
|
|
var exprType = tryExpr.Type;
|
|
var returnsResult = exprType != typeof(void) && (containsReturnGotoExpression || !parent.IgnoresResult());
|
|
var resultVarIndex = -1;
|
|
|
|
if (returnsResult)
|
|
EmitStoreLocalVariable(il, resultVarIndex = il.GetNextLocalVarIndex(exprType));
|
|
|
|
var catchBlocks = tryExpr.Handlers;
|
|
for (var i = 0; i < catchBlocks.Count; i++)
|
|
{
|
|
var catchBlock = catchBlocks[i];
|
|
if (catchBlock.Filter != null)
|
|
return false; // todo: Add support for filters in catch expression
|
|
|
|
il.BeginCatchBlock(catchBlock.Test);
|
|
|
|
// at the beginning of catch the Exception value is on the stack,
|
|
// we will store into local variable.
|
|
var exVarExpr = catchBlock.Variable;
|
|
if (exVarExpr != null)
|
|
{
|
|
var exVarIndex = il.GetNextLocalVarIndex(exVarExpr.Type);
|
|
closure.PushBlockWithVars(exVarExpr, exVarIndex);
|
|
EmitStoreLocalVariable(il, exVarIndex);
|
|
}
|
|
|
|
if (!TryEmit(catchBlock.Body, paramExprs, il, ref closure, parent))
|
|
return false;
|
|
|
|
if (exVarExpr != null)
|
|
closure.PopBlock();
|
|
|
|
if (returnsResult)
|
|
EmitStoreLocalVariable(il, resultVarIndex);
|
|
}
|
|
|
|
var finallyExpr = tryExpr.Finally;
|
|
if (finallyExpr != null)
|
|
{
|
|
il.BeginFinallyBlock();
|
|
if (!TryEmit(finallyExpr, paramExprs, il, ref closure, parent))
|
|
return false;
|
|
}
|
|
|
|
il.EndExceptionBlock();
|
|
|
|
if (returnsResult)
|
|
EmitLoadLocalVariable(il, resultVarIndex);
|
|
|
|
--closure.CurrentTryCatchFinallyIndex;
|
|
return true;
|
|
}
|
|
|
|
private static bool TryEmitParameter(ParameterExpression paramExpr,
|
|
IReadOnlyList<ParameterExpression> paramExprs, ILGenerator il, ref ClosureInfo closure,
|
|
ParentFlags parent, int byRefIndex = -1)
|
|
{
|
|
// if parameter is passed through, then just load it on stack
|
|
var paramType = paramExpr.Type;
|
|
|
|
var paramIndex = paramExprs.Count - 1;
|
|
while (paramIndex != -1 && !ReferenceEquals(paramExprs[paramIndex], paramExpr))
|
|
--paramIndex;
|
|
if (paramIndex != -1)
|
|
{
|
|
if ((closure.Status & ClosureStatus.ShouldBeStaticMethod) == 0)
|
|
++paramIndex; // shift parameter index by one, because the first one will be closure
|
|
|
|
closure.LastEmitIsAddress = !paramExpr.IsByRef && paramType.IsValueType() &&
|
|
((parent & ParentFlags.InstanceCall) == ParentFlags.InstanceCall ||
|
|
(parent & ParentFlags.MemberAccess) != 0);
|
|
|
|
if (closure.LastEmitIsAddress)
|
|
il.Emit(OpCodes.Ldarga_S, (byte)paramIndex);
|
|
else
|
|
{
|
|
if (paramIndex == 0)
|
|
il.Emit(OpCodes.Ldarg_0);
|
|
else if (paramIndex == 1)
|
|
il.Emit(OpCodes.Ldarg_1);
|
|
else if (paramIndex == 2)
|
|
il.Emit(OpCodes.Ldarg_2);
|
|
else if (paramIndex == 3)
|
|
il.Emit(OpCodes.Ldarg_3);
|
|
else
|
|
il.Emit(OpCodes.Ldarg_S, (byte)paramIndex);
|
|
}
|
|
|
|
if (paramExpr.IsByRef)
|
|
{
|
|
if ((parent & ParentFlags.MemberAccess) != 0 && paramType.IsClass() ||
|
|
(parent & ParentFlags.Coalesce) != 0)
|
|
il.Emit(OpCodes.Ldind_Ref);
|
|
else if ((parent & ParentFlags.Arithmetic) != 0)
|
|
EmitDereference(il, paramType);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
// If parameter isn't passed, then it is passed into some outer lambda or it is a local variable,
|
|
// so it should be loaded from closure or from the locals. Then the closure is null will be an invalid state.
|
|
// Parameter may represent a variable, so first look if this is the case
|
|
var varIndex = closure.GetDefinedLocalVarOrDefault(paramExpr);
|
|
if (varIndex != -1)
|
|
{
|
|
if (byRefIndex != -1 ||
|
|
paramType.IsValueType() && (parent & (ParentFlags.MemberAccess | ParentFlags.InstanceAccess)) != 0)
|
|
EmitLoadLocalVariableAddress(il, varIndex);
|
|
else
|
|
EmitLoadLocalVariable(il, varIndex);
|
|
return true;
|
|
}
|
|
|
|
if (paramExpr.IsByRef)
|
|
{
|
|
EmitLoadLocalVariableAddress(il, byRefIndex);
|
|
return true;
|
|
}
|
|
|
|
// the only possibility that we are here is because we are in the nested lambda,
|
|
// and it uses the parameter or variable from the outer lambda
|
|
var nonPassedParams = closure.NonPassedParameters;
|
|
var nonPassedParamIndex = nonPassedParams.Length - 1;
|
|
while (nonPassedParamIndex != -1 && !ReferenceEquals(nonPassedParams[nonPassedParamIndex], paramExpr))
|
|
--nonPassedParamIndex;
|
|
if (nonPassedParamIndex == -1)
|
|
return false; // what??? no chance
|
|
|
|
// Load non-passed argument from Closure - closure object is always a first argument
|
|
il.Emit(OpCodes.Ldarg_0);
|
|
il.Emit(OpCodes.Ldfld, ArrayClosureWithNonPassedParamsField);
|
|
EmitLoadConstantInt(il, nonPassedParamIndex);
|
|
il.Emit(OpCodes.Ldelem_Ref);
|
|
|
|
// source type is object, NonPassedParams is object array
|
|
if (paramType.IsValueType())
|
|
il.Emit(OpCodes.Unbox_Any, paramType);
|
|
|
|
return true;
|
|
}
|
|
|
|
private static void EmitDereference(ILGenerator il, Type type)
|
|
{
|
|
if (type == typeof(Int32))
|
|
il.Emit(OpCodes.Ldind_I4);
|
|
else if (type == typeof(Int64))
|
|
il.Emit(OpCodes.Ldind_I8);
|
|
else if (type == typeof(Int16))
|
|
il.Emit(OpCodes.Ldind_I2);
|
|
else if (type == typeof(SByte))
|
|
il.Emit(OpCodes.Ldind_I1);
|
|
else if (type == typeof(Single))
|
|
il.Emit(OpCodes.Ldind_R4);
|
|
else if (type == typeof(Double))
|
|
il.Emit(OpCodes.Ldind_R8);
|
|
else if (type == typeof(IntPtr))
|
|
il.Emit(OpCodes.Ldind_I);
|
|
else if (type == typeof(UIntPtr))
|
|
il.Emit(OpCodes.Ldind_I);
|
|
else if (type == typeof(Byte))
|
|
il.Emit(OpCodes.Ldind_U1);
|
|
else if (type == typeof(UInt16))
|
|
il.Emit(OpCodes.Ldind_U2);
|
|
else if (type == typeof(UInt32))
|
|
il.Emit(OpCodes.Ldind_U4);
|
|
else
|
|
il.Emit(OpCodes.Ldobj, type);
|
|
//todo: UInt64 as there is no OpCodes? Ldind_Ref?
|
|
}
|
|
|
|
private static bool TryEmitSimpleUnaryExpression(UnaryExpression expr,
|
|
IReadOnlyList<ParameterExpression> paramExprs, ILGenerator il, ref ClosureInfo closure,
|
|
ParentFlags parent)
|
|
{
|
|
var exprType = expr.Type;
|
|
|
|
// todo: support decimal here
|
|
if (exprType == typeof(decimal))
|
|
return false;
|
|
|
|
if (!TryEmit(expr.Operand, paramExprs, il, ref closure, parent))
|
|
return false;
|
|
|
|
if ((parent & ParentFlags.IgnoreResult) != 0)
|
|
il.Emit(OpCodes.Pop);
|
|
else
|
|
{
|
|
if (expr.NodeType == ExpressionType.TypeAs)
|
|
{
|
|
il.Emit(OpCodes.Isinst, exprType);
|
|
}
|
|
else if (expr.NodeType == ExpressionType.IsFalse)
|
|
{
|
|
var falseLabel = il.DefineLabel();
|
|
var continueLabel = il.DefineLabel();
|
|
il.Emit(OpCodes.Brfalse, falseLabel);
|
|
il.Emit(OpCodes.Ldc_I4_0);
|
|
il.Emit(OpCodes.Br, continueLabel);
|
|
il.MarkLabel(falseLabel);
|
|
il.Emit(OpCodes.Ldc_I4_1);
|
|
il.MarkLabel(continueLabel);
|
|
}
|
|
else if (expr.NodeType == ExpressionType.Increment)
|
|
{
|
|
if (!TryEmitNumberOne(il, exprType))
|
|
return false;
|
|
il.Emit(OpCodes.Add);
|
|
}
|
|
else if (expr.NodeType == ExpressionType.Decrement)
|
|
{
|
|
if (!TryEmitNumberOne(il, exprType))
|
|
return false;
|
|
il.Emit(OpCodes.Sub);
|
|
}
|
|
else if (expr.NodeType == ExpressionType.Negate || expr.NodeType == ExpressionType.NegateChecked)
|
|
{
|
|
il.Emit(OpCodes.Neg);
|
|
}
|
|
else if (expr.NodeType == ExpressionType.OnesComplement)
|
|
{
|
|
il.Emit(OpCodes.Not);
|
|
}
|
|
else if (expr.NodeType == ExpressionType.Unbox)
|
|
{
|
|
il.Emit(OpCodes.Unbox_Any, exprType);
|
|
}
|
|
else if (expr.NodeType == ExpressionType.IsTrue)
|
|
{ }
|
|
else if (expr.NodeType == ExpressionType.UnaryPlus)
|
|
{ }
|
|
}
|
|
return true;
|
|
}
|
|
|
|
private static bool TryEmitTypeIs(TypeBinaryExpression expr,
|
|
IReadOnlyList<ParameterExpression> paramExprs, ILGenerator il, ref ClosureInfo closure,
|
|
ParentFlags parent)
|
|
{
|
|
if (!TryEmit(expr.Expression, paramExprs, il, ref closure, parent))
|
|
return false;
|
|
|
|
if ((parent & ParentFlags.IgnoreResult) != 0)
|
|
il.Emit(OpCodes.Pop);
|
|
else
|
|
{
|
|
il.Emit(OpCodes.Isinst, expr.TypeOperand);
|
|
il.Emit(OpCodes.Ldnull);
|
|
il.Emit(OpCodes.Cgt_Un);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
private static bool TryEmitNot(UnaryExpression expr,
|
|
IReadOnlyList<ParameterExpression> paramExprs, ILGenerator il, ref ClosureInfo closure,
|
|
ParentFlags parent)
|
|
{
|
|
if (!TryEmit(expr.Operand, paramExprs, il, ref closure, parent))
|
|
return false;
|
|
if ((parent & ParentFlags.IgnoreResult) > 0)
|
|
il.Emit(OpCodes.Pop);
|
|
else
|
|
{
|
|
if (expr.Type == typeof(bool))
|
|
{
|
|
il.Emit(OpCodes.Ldc_I4_0);
|
|
il.Emit(OpCodes.Ceq);
|
|
}
|
|
else
|
|
{
|
|
il.Emit(OpCodes.Not);
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
private static bool TryEmitConvert(UnaryExpression expr,
|
|
IReadOnlyList<ParameterExpression> paramExprs, ILGenerator il, ref ClosureInfo closure, ParentFlags parent)
|
|
{
|
|
var opExpr = expr.Operand;
|
|
var method = expr.Method;
|
|
if (method != null && method.Name != "op_Implicit" && method.Name != "op_Explicit")
|
|
{
|
|
if (!TryEmit(opExpr, paramExprs, il, ref closure, parent & ~ParentFlags.IgnoreResult | ParentFlags.InstanceCall, 0))
|
|
return false;
|
|
|
|
il.Emit(method.IsVirtual ? OpCodes.Callvirt : OpCodes.Call, method);
|
|
if ((parent & ParentFlags.IgnoreResult) != 0 && method.ReturnType != typeof(void))
|
|
il.Emit(OpCodes.Pop);
|
|
return true;
|
|
}
|
|
|
|
var sourceType = opExpr.Type;
|
|
var sourceTypeIsNullable = sourceType.IsNullable();
|
|
var underlyingNullableSourceType = Nullable.GetUnderlyingType(sourceType);
|
|
var targetType = expr.Type;
|
|
|
|
if (sourceTypeIsNullable && targetType == underlyingNullableSourceType)
|
|
{
|
|
if (!TryEmit(opExpr, paramExprs, il, ref closure, parent & ~ParentFlags.IgnoreResult | ParentFlags.InstanceAccess))
|
|
return false;
|
|
|
|
if (!closure.LastEmitIsAddress)
|
|
EmitStoreLocalVariableAndLoadItsAddress(il, sourceType);
|
|
|
|
il.Emit(OpCodes.Call, sourceType.FindValueGetterMethod());
|
|
|
|
if ((parent & ParentFlags.IgnoreResult) != 0)
|
|
il.Emit(OpCodes.Pop);
|
|
return true;
|
|
}
|
|
|
|
if (!TryEmit(opExpr, paramExprs, il, ref closure, parent & ~ParentFlags.IgnoreResult & ~ParentFlags.InstanceAccess))
|
|
return false;
|
|
|
|
var targetTypeIsNullable = targetType.IsNullable();
|
|
var underlyingNullableTargetType = Nullable.GetUnderlyingType(targetType);
|
|
if (targetTypeIsNullable && sourceType == underlyingNullableTargetType)
|
|
{
|
|
il.Emit(OpCodes.Newobj, targetType.GetTypeInfo().DeclaredConstructors.GetFirst());
|
|
return true;
|
|
}
|
|
|
|
if (sourceType == targetType || targetType == typeof(object))
|
|
{
|
|
if (targetType == typeof(object) && sourceType.IsValueType())
|
|
il.Emit(OpCodes.Box, sourceType);
|
|
if (IgnoresResult(parent))
|
|
il.Emit(OpCodes.Pop);
|
|
return true;
|
|
}
|
|
|
|
// check implicit / explicit conversion operators on source and target types
|
|
// for non-primitives and for non-primitive nullable - #73
|
|
if (!sourceTypeIsNullable && !sourceType.IsPrimitive())
|
|
{
|
|
var actualTargetType = targetTypeIsNullable ? underlyingNullableTargetType : targetType;
|
|
var convertOpMethod = method ?? sourceType.FindConvertOperator(sourceType, actualTargetType);
|
|
if (convertOpMethod != null)
|
|
{
|
|
il.Emit(OpCodes.Call, convertOpMethod);
|
|
|
|
if (targetTypeIsNullable)
|
|
il.Emit(OpCodes.Newobj, targetType.GetTypeInfo().DeclaredConstructors.GetFirst());
|
|
|
|
if ((parent & ParentFlags.IgnoreResult) != 0)
|
|
il.Emit(OpCodes.Pop);
|
|
|
|
return true;
|
|
}
|
|
}
|
|
else if (!targetTypeIsNullable)
|
|
{
|
|
var actualSourceType = sourceTypeIsNullable ? underlyingNullableSourceType : sourceType;
|
|
|
|
var convertOpMethod = method ?? actualSourceType.FindConvertOperator(actualSourceType, targetType);
|
|
if (convertOpMethod != null)
|
|
{
|
|
if (sourceTypeIsNullable)
|
|
{
|
|
EmitStoreLocalVariableAndLoadItsAddress(il, sourceType);
|
|
il.Emit(OpCodes.Call, sourceType.FindValueGetterMethod());
|
|
}
|
|
|
|
il.Emit(OpCodes.Call, convertOpMethod);
|
|
if ((parent & ParentFlags.IgnoreResult) != 0)
|
|
il.Emit(OpCodes.Pop);
|
|
|
|
return true;
|
|
}
|
|
}
|
|
|
|
if (!targetTypeIsNullable && !targetType.IsPrimitive())
|
|
{
|
|
var actualSourceType = sourceTypeIsNullable ? underlyingNullableSourceType : sourceType;
|
|
|
|
// ReSharper disable once ConstantNullCoalescingCondition
|
|
var convertOpMethod = method ?? targetType.FindConvertOperator(actualSourceType, targetType);
|
|
if (convertOpMethod != null)
|
|
{
|
|
if (sourceTypeIsNullable)
|
|
{
|
|
EmitStoreLocalVariableAndLoadItsAddress(il, sourceType);
|
|
il.Emit(OpCodes.Call, sourceType.FindValueGetterMethod());
|
|
}
|
|
|
|
il.Emit(OpCodes.Call, convertOpMethod);
|
|
if ((parent & ParentFlags.IgnoreResult) != 0)
|
|
il.Emit(OpCodes.Pop);
|
|
|
|
return true;
|
|
}
|
|
}
|
|
else if (!sourceTypeIsNullable)
|
|
{
|
|
var actualTargetType = targetTypeIsNullable ? underlyingNullableTargetType : targetType;
|
|
var convertOpMethod = method ?? actualTargetType.FindConvertOperator(sourceType, actualTargetType);
|
|
if (convertOpMethod != null)
|
|
{
|
|
il.Emit(OpCodes.Call, convertOpMethod);
|
|
|
|
if (targetTypeIsNullable)
|
|
il.Emit(OpCodes.Newobj, targetType.GetTypeInfo().DeclaredConstructors.GetFirst());
|
|
|
|
if ((parent & ParentFlags.IgnoreResult) != 0)
|
|
il.Emit(OpCodes.Pop);
|
|
|
|
return true;
|
|
}
|
|
}
|
|
|
|
if (sourceType == typeof(object) && targetType.IsValueType())
|
|
{
|
|
il.Emit(OpCodes.Unbox_Any, targetType);
|
|
}
|
|
else if (targetTypeIsNullable)
|
|
{
|
|
// Conversion to Nullable: `new Nullable<T>(T val);`
|
|
if (!sourceTypeIsNullable)
|
|
{
|
|
if (!TryEmitValueConvert(underlyingNullableTargetType, il, isChecked: false))
|
|
return false;
|
|
|
|
il.Emit(OpCodes.Newobj, targetType.GetTypeInfo().DeclaredConstructors.GetFirst());
|
|
}
|
|
else
|
|
{
|
|
var sourceVarIndex = EmitStoreLocalVariableAndLoadItsAddress(il, sourceType);
|
|
il.Emit(OpCodes.Call, sourceType.FindNullableHasValueGetterMethod());
|
|
|
|
var labelSourceHasValue = il.DefineLabel();
|
|
il.Emit(OpCodes.Brtrue_S, labelSourceHasValue); // jump where source has a value
|
|
|
|
// otherwise, emit and load a `new Nullable<TTarget>()` struct (that's why a Init instead of New)
|
|
EmitLoadLocalVariable(il, InitValueTypeVariable(il, targetType));
|
|
|
|
// jump to completion
|
|
var labelDone = il.DefineLabel();
|
|
il.Emit(OpCodes.Br_S, labelDone);
|
|
|
|
// if source nullable has a value:
|
|
il.MarkLabel(labelSourceHasValue);
|
|
EmitLoadLocalVariableAddress(il, sourceVarIndex);
|
|
il.Emit(OpCodes.Call, sourceType.FindNullableGetValueOrDefaultMethod());
|
|
|
|
if (!TryEmitValueConvert(underlyingNullableTargetType, il,
|
|
expr.NodeType == ExpressionType.ConvertChecked))
|
|
{
|
|
var convertOpMethod = method ?? underlyingNullableTargetType.FindConvertOperator(underlyingNullableSourceType, underlyingNullableTargetType);
|
|
if (convertOpMethod == null)
|
|
return false; // nor conversion nor conversion operator is found
|
|
il.Emit(OpCodes.Call, convertOpMethod);
|
|
}
|
|
|
|
il.Emit(OpCodes.Newobj, targetType.GetTypeInfo().DeclaredConstructors.GetFirst());
|
|
il.MarkLabel(labelDone);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (targetType.GetTypeInfo().IsEnum)
|
|
targetType = Enum.GetUnderlyingType(targetType);
|
|
|
|
// fixes #159
|
|
if (sourceTypeIsNullable)
|
|
{
|
|
EmitStoreLocalVariableAndLoadItsAddress(il, sourceType);
|
|
il.Emit(OpCodes.Call, sourceType.FindValueGetterMethod());
|
|
}
|
|
|
|
// cast as the last resort and let's it fail if unlucky
|
|
if (!TryEmitValueConvert(targetType, il, expr.NodeType == ExpressionType.ConvertChecked))
|
|
il.Emit(OpCodes.Castclass, targetType);
|
|
}
|
|
|
|
if ((parent & ParentFlags.IgnoreResult) != 0)
|
|
il.Emit(OpCodes.Pop);
|
|
|
|
return true;
|
|
}
|
|
|
|
private static bool TryEmitValueConvert(Type targetType, ILGenerator il, bool isChecked)
|
|
{
|
|
if (targetType == typeof(int))
|
|
il.Emit(isChecked ? OpCodes.Conv_Ovf_I4 : OpCodes.Conv_I4);
|
|
else if (targetType == typeof(float))
|
|
il.Emit(OpCodes.Conv_R4);
|
|
else if (targetType == typeof(uint))
|
|
il.Emit(isChecked ? OpCodes.Conv_Ovf_U4 : OpCodes.Conv_U4);
|
|
else if (targetType == typeof(sbyte))
|
|
il.Emit(isChecked ? OpCodes.Conv_Ovf_I1 : OpCodes.Conv_I1);
|
|
else if (targetType == typeof(byte))
|
|
il.Emit(isChecked ? OpCodes.Conv_Ovf_U1 : OpCodes.Conv_U1);
|
|
else if (targetType == typeof(short))
|
|
il.Emit(isChecked ? OpCodes.Conv_Ovf_I2 : OpCodes.Conv_I2);
|
|
else if (targetType == typeof(ushort) || targetType == typeof(char))
|
|
il.Emit(isChecked ? OpCodes.Conv_Ovf_U2 : OpCodes.Conv_U2);
|
|
else if (targetType == typeof(long))
|
|
il.Emit(isChecked ? OpCodes.Conv_Ovf_I8 : OpCodes.Conv_I8);
|
|
else if (targetType == typeof(ulong))
|
|
il.Emit(isChecked ? OpCodes.Conv_Ovf_U8 : OpCodes.Conv_U8);
|
|
else if (targetType == typeof(double))
|
|
il.Emit(OpCodes.Conv_R8);
|
|
else
|
|
return false;
|
|
return true;
|
|
}
|
|
|
|
private static bool TryEmitNotNullConstant(
|
|
bool considerClosure, Type exprType, object constantValue, ILGenerator il, ref ClosureInfo closure)
|
|
{
|
|
var constValueType = constantValue.GetType();
|
|
if (considerClosure && IsClosureBoundConstant(constantValue, constValueType.GetTypeInfo()))
|
|
{
|
|
var constItems = closure.Constants.Items;
|
|
var constIndex = closure.Constants.Count - 1;
|
|
while (constIndex != -1 && !ReferenceEquals(constItems[constIndex], constantValue))
|
|
--constIndex;
|
|
if (constIndex == -1)
|
|
return false;
|
|
|
|
var varIndex = closure.ConstantUsage.Items[constIndex] - 1;
|
|
if (varIndex > 0)
|
|
EmitLoadLocalVariable(il, varIndex);
|
|
else
|
|
{
|
|
il.Emit(OpCodes.Ldloc_0); // load constants array from variable
|
|
EmitLoadConstantInt(il, constIndex);
|
|
il.Emit(OpCodes.Ldelem_Ref);
|
|
if (exprType.IsValueType())
|
|
il.Emit(OpCodes.Unbox_Any, exprType);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (constantValue is string s)
|
|
{
|
|
il.Emit(OpCodes.Ldstr, s);
|
|
return true;
|
|
}
|
|
|
|
if (constantValue is Type t)
|
|
{
|
|
il.Emit(OpCodes.Ldtoken, t);
|
|
il.Emit(OpCodes.Call, _getTypeFromHandleMethod);
|
|
return true;
|
|
}
|
|
|
|
// get raw enum type to light
|
|
if (constValueType.GetTypeInfo().IsEnum)
|
|
constValueType = Enum.GetUnderlyingType(constValueType);
|
|
|
|
if (!TryEmitNumberConstant(il, constantValue, constValueType))
|
|
return false;
|
|
}
|
|
|
|
var underlyingNullableType = Nullable.GetUnderlyingType(exprType);
|
|
if (underlyingNullableType != null)
|
|
il.Emit(OpCodes.Newobj, exprType.GetTypeInfo().DeclaredConstructors.GetFirst());
|
|
|
|
// boxing the value type, otherwise we can get a strange result when 0 is treated as Null.
|
|
else if (exprType == typeof(object) && constValueType.IsValueType())
|
|
il.Emit(OpCodes.Box, constantValue.GetType()); // using normal type for Enum instead of underlying type
|
|
|
|
return true;
|
|
}
|
|
|
|
// todo: can we do something about boxing?
|
|
private static bool TryEmitNumberConstant(ILGenerator il, object constantValue, Type constValueType)
|
|
{
|
|
if (constValueType == typeof(int))
|
|
{
|
|
EmitLoadConstantInt(il, (int)constantValue);
|
|
}
|
|
else if (constValueType == typeof(char))
|
|
{
|
|
EmitLoadConstantInt(il, (char)constantValue);
|
|
}
|
|
else if (constValueType == typeof(short))
|
|
{
|
|
EmitLoadConstantInt(il, (short)constantValue);
|
|
}
|
|
else if (constValueType == typeof(byte))
|
|
{
|
|
EmitLoadConstantInt(il, (byte)constantValue);
|
|
}
|
|
else if (constValueType == typeof(ushort))
|
|
{
|
|
EmitLoadConstantInt(il, (ushort)constantValue);
|
|
}
|
|
else if (constValueType == typeof(sbyte))
|
|
{
|
|
EmitLoadConstantInt(il, (sbyte)constantValue);
|
|
}
|
|
else if (constValueType == typeof(uint))
|
|
{
|
|
unchecked
|
|
{
|
|
EmitLoadConstantInt(il, (int)(uint)constantValue);
|
|
}
|
|
}
|
|
else if (constValueType == typeof(long))
|
|
{
|
|
il.Emit(OpCodes.Ldc_I8, (long)constantValue);
|
|
}
|
|
else if (constValueType == typeof(ulong))
|
|
{
|
|
unchecked
|
|
{
|
|
il.Emit(OpCodes.Ldc_I8, (long)(ulong)constantValue);
|
|
}
|
|
}
|
|
else if (constValueType == typeof(float))
|
|
{
|
|
il.Emit(OpCodes.Ldc_R4, (float)constantValue);
|
|
}
|
|
else if (constValueType == typeof(double))
|
|
{
|
|
il.Emit(OpCodes.Ldc_R8, (double)constantValue);
|
|
}
|
|
else if (constValueType == typeof(bool))
|
|
{
|
|
il.Emit((bool)constantValue ? OpCodes.Ldc_I4_1 : OpCodes.Ldc_I4_0);
|
|
}
|
|
else if (constValueType == typeof(IntPtr))
|
|
{
|
|
il.Emit(OpCodes.Ldc_I8, ((IntPtr)constantValue).ToInt64());
|
|
}
|
|
else if (constValueType == typeof(UIntPtr))
|
|
{
|
|
unchecked
|
|
{
|
|
il.Emit(OpCodes.Ldc_I8, (long)((UIntPtr)constantValue).ToUInt64());
|
|
}
|
|
}
|
|
else if (constValueType == typeof(decimal))
|
|
{
|
|
EmitDecimalConstant((decimal)constantValue, il);
|
|
}
|
|
else
|
|
{
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
internal static bool TryEmitNumberOne(ILGenerator il, Type type)
|
|
{
|
|
if (type == typeof(int) || type == typeof(char) || type == typeof(short) ||
|
|
type == typeof(byte) || type == typeof(ushort) || type == typeof(sbyte) ||
|
|
type == typeof(uint))
|
|
{
|
|
il.Emit(OpCodes.Ldc_I4_1);
|
|
}
|
|
else if (type == typeof(long) || type == typeof(ulong) ||
|
|
type == typeof(IntPtr) || type == typeof(UIntPtr))
|
|
{
|
|
il.Emit(OpCodes.Ldc_I8, (long)1);
|
|
}
|
|
else if (type == typeof(float))
|
|
{
|
|
il.Emit(OpCodes.Ldc_R4, 1f);
|
|
}
|
|
else if (type == typeof(double))
|
|
{
|
|
il.Emit(OpCodes.Ldc_R8, 1d);
|
|
}
|
|
else
|
|
{
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
internal static void EmitLoadConstantsAndNestedLambdasIntoVars(ILGenerator il, ref ClosureInfo closure)
|
|
{
|
|
// Load constants array field from Closure and store it into the variable
|
|
il.Emit(OpCodes.Ldarg_0);
|
|
il.Emit(OpCodes.Ldfld, ArrayClosureArrayField);
|
|
EmitStoreLocalVariable(il, il.GetNextLocalVarIndex(typeof(object[])));
|
|
|
|
var constItems = closure.Constants.Items;
|
|
var constCount = closure.Constants.Count;
|
|
var constUsage = closure.ConstantUsage.Items;
|
|
|
|
int varIndex;
|
|
for (var i = 0; i < constCount; i++)
|
|
{
|
|
if (constUsage[i] > 1)
|
|
{
|
|
il.Emit(OpCodes.Ldloc_0);// load array field variable on a stack
|
|
EmitLoadConstantInt(il, i);
|
|
il.Emit(OpCodes.Ldelem_Ref);
|
|
|
|
var varType = constItems[i].GetType();
|
|
if (varType.IsValueType())
|
|
il.Emit(OpCodes.Unbox_Any, varType);
|
|
|
|
varIndex = il.GetNextLocalVarIndex(varType);
|
|
constUsage[i] = varIndex + 1; // to distinguish from the default 1
|
|
EmitStoreLocalVariable(il, varIndex);
|
|
}
|
|
}
|
|
|
|
var nestedLambdas = closure.NestedLambdas;
|
|
for (var i = 0; i < nestedLambdas.Length; i++)
|
|
{
|
|
var nestedLambda = nestedLambdas[i];
|
|
if (nestedLambda.UsageCountOrVarIndex > 1)
|
|
{
|
|
il.Emit(OpCodes.Ldloc_0);// load array field variable on a stack
|
|
EmitLoadConstantInt(il, constCount + i);
|
|
il.Emit(OpCodes.Ldelem_Ref);
|
|
varIndex = il.GetNextLocalVarIndex(nestedLambda.Lambda.GetType());
|
|
nestedLambda.UsageCountOrVarIndex = varIndex + 1;
|
|
EmitStoreLocalVariable(il, varIndex);
|
|
}
|
|
}
|
|
}
|
|
|
|
private static void EmitDecimalConstant(decimal value, ILGenerator il)
|
|
{
|
|
//check if decimal has decimal places, if not use shorter IL code (constructor from int or long)
|
|
if (value % 1 == 0)
|
|
{
|
|
if (value >= int.MinValue && value <= int.MaxValue)
|
|
{
|
|
EmitLoadConstantInt(il, decimal.ToInt32(value));
|
|
il.Emit(OpCodes.Newobj, typeof(decimal).FindSingleParamConstructor(typeof(int)));
|
|
return;
|
|
}
|
|
|
|
if (value >= long.MinValue && value <= long.MaxValue)
|
|
{
|
|
il.Emit(OpCodes.Ldc_I8, decimal.ToInt64(value));
|
|
il.Emit(OpCodes.Newobj, typeof(decimal).FindSingleParamConstructor(typeof(long)));
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (value == decimal.MinValue)
|
|
{
|
|
il.Emit(OpCodes.Ldsfld, typeof(decimal).GetTypeInfo().GetDeclaredField(nameof(decimal.MinValue)));
|
|
return;
|
|
}
|
|
|
|
if (value == decimal.MaxValue)
|
|
{
|
|
il.Emit(OpCodes.Ldsfld, typeof(decimal).GetTypeInfo().GetDeclaredField(nameof(decimal.MaxValue)));
|
|
return;
|
|
}
|
|
|
|
var parts = decimal.GetBits(value);
|
|
var sign = (parts[3] & 0x80000000) != 0;
|
|
var scale = (byte)((parts[3] >> 16) & 0x7F);
|
|
|
|
EmitLoadConstantInt(il, parts[0]);
|
|
EmitLoadConstantInt(il, parts[1]);
|
|
EmitLoadConstantInt(il, parts[2]);
|
|
|
|
il.Emit(sign ? OpCodes.Ldc_I4_1 : OpCodes.Ldc_I4_0);
|
|
EmitLoadConstantInt(il, scale);
|
|
|
|
il.Emit(OpCodes.Conv_U1);
|
|
|
|
il.Emit(OpCodes.Newobj, _decimalCtor.Value);
|
|
}
|
|
|
|
private static readonly Lazy<ConstructorInfo> _decimalCtor = new Lazy<ConstructorInfo>(() =>
|
|
{
|
|
foreach (var ctor in typeof(decimal).GetTypeInfo().DeclaredConstructors)
|
|
if (ctor.GetParameters().Length == 5)
|
|
return ctor;
|
|
return null;
|
|
});
|
|
|
|
private static int InitValueTypeVariable(ILGenerator il, Type exprType)
|
|
{
|
|
var locVarIndex = il.GetNextLocalVarIndex(exprType);
|
|
EmitLoadLocalVariableAddress(il, locVarIndex);
|
|
il.Emit(OpCodes.Initobj, exprType);
|
|
return locVarIndex;
|
|
}
|
|
|
|
private static bool EmitNewArray(NewArrayExpression expr,
|
|
IReadOnlyList<ParameterExpression> paramExprs, ILGenerator il, ref ClosureInfo closure, ParentFlags parent)
|
|
{
|
|
var arrayType = expr.Type;
|
|
var elems = expr.Expressions;
|
|
var elemType = arrayType.GetElementType();
|
|
if (elemType == null)
|
|
return false;
|
|
|
|
var rank = arrayType.GetArrayRank();
|
|
if (rank == 1) // one dimensional
|
|
{
|
|
EmitLoadConstantInt(il, elems.Count);
|
|
}
|
|
else // multi dimensional
|
|
{
|
|
for (var i = 0; i < elems.Count; i++)
|
|
if (!TryEmit(elems[i], paramExprs, il, ref closure, parent, i))
|
|
return false;
|
|
|
|
il.Emit(OpCodes.Newobj, arrayType.GetTypeInfo().DeclaredConstructors.GetFirst());
|
|
return true;
|
|
}
|
|
|
|
il.Emit(OpCodes.Newarr, elemType);
|
|
|
|
var isElemOfValueType = elemType.IsValueType();
|
|
|
|
for (int i = 0, n = elems.Count; i < n; i++)
|
|
{
|
|
il.Emit(OpCodes.Dup);
|
|
EmitLoadConstantInt(il, i);
|
|
|
|
// loading element address for later copying of value into it.
|
|
if (isElemOfValueType)
|
|
il.Emit(OpCodes.Ldelema, elemType);
|
|
|
|
if (!TryEmit(elems[i], paramExprs, il, ref closure, parent))
|
|
return false;
|
|
|
|
if (isElemOfValueType)
|
|
il.Emit(OpCodes.Stobj, elemType); // store element of value type by array element address
|
|
else
|
|
il.Emit(OpCodes.Stelem_Ref);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
private static bool TryEmitArrayIndex(Type exprType, ILGenerator il)
|
|
{
|
|
if (exprType.IsValueType())
|
|
il.Emit(OpCodes.Ldelem, exprType);
|
|
else
|
|
il.Emit(OpCodes.Ldelem_Ref);
|
|
return true;
|
|
}
|
|
|
|
private static bool EmitMemberInit(MemberInitExpression expr,
|
|
IReadOnlyList<ParameterExpression> paramExprs, ILGenerator il, ref ClosureInfo closure, ParentFlags parent)
|
|
{
|
|
var valueVarIndex = -1;
|
|
if (expr.Type.IsValueType())
|
|
valueVarIndex = il.GetNextLocalVarIndex(expr.Type);
|
|
|
|
var newExpr = expr.NewExpression;
|
|
if (newExpr == null)
|
|
{
|
|
if (!TryEmit(expr.Expression, paramExprs, il, ref closure, parent))
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
var argExprs = newExpr.Arguments;
|
|
for (var i = 0; i < argExprs.Count; i++)
|
|
if (!TryEmit(argExprs[i], paramExprs, il, ref closure, parent, i))
|
|
return false;
|
|
|
|
var ctor = newExpr.Constructor;
|
|
// ReSharper disable once ConditionIsAlwaysTrueOrFalse
|
|
if (ctor != null)
|
|
il.Emit(OpCodes.Newobj, ctor);
|
|
else if (newExpr.Type.IsValueType())
|
|
{
|
|
if (valueVarIndex == -1)
|
|
valueVarIndex = il.GetNextLocalVarIndex(expr.Type);
|
|
EmitLoadLocalVariableAddress(il, valueVarIndex);
|
|
il.Emit(OpCodes.Initobj, newExpr.Type);
|
|
}
|
|
else
|
|
return false; // null constructor and not a value type, better to fallback
|
|
}
|
|
|
|
var bindings = expr.Bindings;
|
|
for (var i = 0; i < bindings.Count; i++)
|
|
{
|
|
var binding = bindings[i];
|
|
if (binding.BindingType != MemberBindingType.Assignment)
|
|
return false;
|
|
|
|
if (valueVarIndex != -1) // load local value address, to set its members
|
|
EmitLoadLocalVariableAddress(il, valueVarIndex);
|
|
else
|
|
il.Emit(OpCodes.Dup); // duplicate member owner on stack
|
|
|
|
if (!TryEmit(((MemberAssignment)binding).Expression, paramExprs, il, ref closure, parent) ||
|
|
!EmitMemberAssign(il, binding.Member))
|
|
return false;
|
|
}
|
|
|
|
if (valueVarIndex != -1)
|
|
EmitLoadLocalVariable(il, valueVarIndex);
|
|
return true;
|
|
}
|
|
|
|
private static bool EmitMemberAssign(ILGenerator il, MemberInfo member)
|
|
{
|
|
if (member is PropertyInfo prop)
|
|
{
|
|
var method = prop.DeclaringType.FindPropertySetMethod(prop.Name);
|
|
if (method == null)
|
|
return false;
|
|
|
|
il.Emit(method.IsVirtual ? OpCodes.Callvirt : OpCodes.Call, method);
|
|
return true;
|
|
}
|
|
|
|
if (member is FieldInfo field)
|
|
{
|
|
il.Emit(field.IsStatic ? OpCodes.Stsfld : OpCodes.Stfld, field);
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
private static bool TryEmitIncDecAssign(UnaryExpression expr,
|
|
IReadOnlyList<ParameterExpression> paramExprs, ILGenerator il, ref ClosureInfo closure, ParentFlags parent)
|
|
{
|
|
var operandExpr = expr.Operand;
|
|
|
|
MemberExpression memberAccess;
|
|
var useLocalVar = false;
|
|
int localVarIndex, paramIndex = -1;
|
|
|
|
var isParameterOrVariable = operandExpr.NodeType == ExpressionType.Parameter;
|
|
var usesResult = (parent & ParentFlags.IgnoreResult) == 0;
|
|
if (isParameterOrVariable)
|
|
{
|
|
localVarIndex = closure.GetDefinedLocalVarOrDefault((ParameterExpression)operandExpr);
|
|
if (localVarIndex != -1)
|
|
{
|
|
EmitLoadLocalVariable(il, localVarIndex);
|
|
useLocalVar = true;
|
|
}
|
|
else
|
|
{
|
|
paramIndex = paramExprs.Count - 1;
|
|
while (paramIndex != -1 && !ReferenceEquals(paramExprs[paramIndex], operandExpr))
|
|
--paramIndex;
|
|
if (paramIndex == -1)
|
|
return false;
|
|
il.Emit(OpCodes.Ldarg, paramIndex + 1);
|
|
}
|
|
|
|
memberAccess = null;
|
|
|
|
}
|
|
else if (operandExpr.NodeType == ExpressionType.MemberAccess)
|
|
{
|
|
memberAccess = (MemberExpression)operandExpr;
|
|
|
|
if (!TryEmitMemberAccess(memberAccess, paramExprs, il, ref closure, parent | ParentFlags.DupMemberOwner))
|
|
return false;
|
|
|
|
useLocalVar = memberAccess.Expression != null && (usesResult || memberAccess.Member is PropertyInfo);
|
|
localVarIndex = useLocalVar ? il.GetNextLocalVarIndex(operandExpr.Type) : -1;
|
|
}
|
|
else
|
|
return false;
|
|
|
|
switch (expr.NodeType)
|
|
{
|
|
case ExpressionType.PreIncrementAssign:
|
|
il.Emit(OpCodes.Ldc_I4_1);
|
|
il.Emit(OpCodes.Add);
|
|
StoreIncDecValue(il, usesResult, isParameterOrVariable, localVarIndex);
|
|
break;
|
|
|
|
case ExpressionType.PostIncrementAssign:
|
|
StoreIncDecValue(il, usesResult, isParameterOrVariable, localVarIndex);
|
|
il.Emit(OpCodes.Ldc_I4_1);
|
|
il.Emit(OpCodes.Add);
|
|
break;
|
|
|
|
case ExpressionType.PreDecrementAssign:
|
|
il.Emit(OpCodes.Ldc_I4_1);
|
|
il.Emit(OpCodes.Sub);
|
|
StoreIncDecValue(il, usesResult, isParameterOrVariable, localVarIndex);
|
|
break;
|
|
|
|
case ExpressionType.PostDecrementAssign:
|
|
StoreIncDecValue(il, usesResult, isParameterOrVariable, localVarIndex);
|
|
il.Emit(OpCodes.Ldc_I4_1);
|
|
il.Emit(OpCodes.Sub);
|
|
break;
|
|
}
|
|
|
|
if (isParameterOrVariable && paramIndex != -1)
|
|
il.Emit(OpCodes.Starg_S, paramIndex + 1);
|
|
else if (isParameterOrVariable || useLocalVar && !usesResult)
|
|
EmitStoreLocalVariable(il, localVarIndex);
|
|
|
|
if (isParameterOrVariable)
|
|
return true;
|
|
|
|
if (useLocalVar && !usesResult)
|
|
EmitLoadLocalVariable(il, localVarIndex);
|
|
|
|
if (!EmitMemberAssign(il, memberAccess.Member))
|
|
return false;
|
|
|
|
if (useLocalVar && usesResult)
|
|
EmitLoadLocalVariable(il, localVarIndex);
|
|
|
|
return true;
|
|
}
|
|
|
|
private static void StoreIncDecValue(ILGenerator il, bool usesResult, bool isVar, int localVarIndex)
|
|
{
|
|
if (!usesResult)
|
|
return;
|
|
|
|
if (isVar || localVarIndex == -1)
|
|
il.Emit(OpCodes.Dup);
|
|
else
|
|
{
|
|
EmitStoreLocalVariable(il, localVarIndex);
|
|
EmitLoadLocalVariable(il, localVarIndex);
|
|
}
|
|
}
|
|
|
|
private static bool TryEmitAssign(BinaryExpression expr,
|
|
IReadOnlyList<ParameterExpression> paramExprs, ILGenerator il, ref ClosureInfo closure, ParentFlags parent)
|
|
{
|
|
var left = expr.Left;
|
|
var right = expr.Right;
|
|
var leftNodeType = expr.Left.NodeType;
|
|
var nodeType = expr.NodeType;
|
|
|
|
// if this assignment is part of a single body-less expression or the result of a block
|
|
// we should put its result to the evaluation stack before the return, otherwise we are
|
|
// somewhere inside the block, so we shouldn't return with the result
|
|
var flags = parent & ~ParentFlags.IgnoreResult;
|
|
switch (leftNodeType)
|
|
{
|
|
case ExpressionType.Parameter:
|
|
var leftParamExpr = (ParameterExpression)left;
|
|
|
|
var paramIndex = paramExprs.Count - 1;
|
|
while (paramIndex != -1 && !ReferenceEquals(paramExprs[paramIndex], leftParamExpr))
|
|
--paramIndex;
|
|
|
|
var arithmeticNodeType = nodeType;
|
|
switch (nodeType)
|
|
{
|
|
case ExpressionType.AddAssign:
|
|
arithmeticNodeType = ExpressionType.Add;
|
|
break;
|
|
case ExpressionType.AddAssignChecked:
|
|
arithmeticNodeType = ExpressionType.AddChecked;
|
|
break;
|
|
case ExpressionType.SubtractAssign:
|
|
arithmeticNodeType = ExpressionType.Subtract;
|
|
break;
|
|
case ExpressionType.SubtractAssignChecked:
|
|
arithmeticNodeType = ExpressionType.SubtractChecked;
|
|
break;
|
|
case ExpressionType.MultiplyAssign:
|
|
arithmeticNodeType = ExpressionType.Multiply;
|
|
break;
|
|
case ExpressionType.MultiplyAssignChecked:
|
|
arithmeticNodeType = ExpressionType.MultiplyChecked;
|
|
break;
|
|
case ExpressionType.DivideAssign:
|
|
arithmeticNodeType = ExpressionType.Divide;
|
|
break;
|
|
case ExpressionType.ModuloAssign:
|
|
arithmeticNodeType = ExpressionType.Modulo;
|
|
break;
|
|
case ExpressionType.PowerAssign:
|
|
arithmeticNodeType = ExpressionType.Power;
|
|
break;
|
|
case ExpressionType.AndAssign:
|
|
arithmeticNodeType = ExpressionType.And;
|
|
break;
|
|
case ExpressionType.OrAssign:
|
|
arithmeticNodeType = ExpressionType.Or;
|
|
break;
|
|
case ExpressionType.ExclusiveOrAssign:
|
|
arithmeticNodeType = ExpressionType.ExclusiveOr;
|
|
break;
|
|
case ExpressionType.LeftShiftAssign:
|
|
arithmeticNodeType = ExpressionType.LeftShift;
|
|
break;
|
|
case ExpressionType.RightShiftAssign:
|
|
arithmeticNodeType = ExpressionType.RightShift;
|
|
break;
|
|
}
|
|
|
|
if (paramIndex != -1)
|
|
{
|
|
// shift parameter index by one, because the first one will be closure
|
|
if ((closure.Status & ClosureStatus.ShouldBeStaticMethod) == 0)
|
|
++paramIndex;
|
|
|
|
if (leftParamExpr.IsByRef)
|
|
{
|
|
if (paramIndex == 0)
|
|
il.Emit(OpCodes.Ldarg_0);
|
|
else if (paramIndex == 1)
|
|
il.Emit(OpCodes.Ldarg_1);
|
|
else if (paramIndex == 2)
|
|
il.Emit(OpCodes.Ldarg_2);
|
|
else if (paramIndex == 3)
|
|
il.Emit(OpCodes.Ldarg_3);
|
|
else
|
|
il.Emit(OpCodes.Ldarg_S, (byte)paramIndex);
|
|
}
|
|
|
|
if (arithmeticNodeType == nodeType)
|
|
{
|
|
if (!TryEmit(right, paramExprs, il, ref closure, flags))
|
|
return false;
|
|
}
|
|
else if (!TryEmitArithmetic(expr, arithmeticNodeType, paramExprs, il, ref closure, parent))
|
|
return false;
|
|
|
|
if ((parent & ParentFlags.IgnoreResult) == 0)
|
|
il.Emit(OpCodes.Dup); // duplicate value to assign and return
|
|
|
|
if (leftParamExpr.IsByRef)
|
|
EmitByRefStore(il, leftParamExpr.Type);
|
|
else
|
|
il.Emit(OpCodes.Starg_S, paramIndex);
|
|
|
|
return true;
|
|
}
|
|
else if (arithmeticNodeType != nodeType)
|
|
{
|
|
var localVarIdx = closure.GetDefinedLocalVarOrDefault(leftParamExpr);
|
|
if (localVarIdx != -1)
|
|
{
|
|
if (!TryEmitArithmetic(expr, arithmeticNodeType, paramExprs, il, ref closure, parent))
|
|
return false;
|
|
|
|
EmitStoreLocalVariable(il, localVarIdx);
|
|
return true;
|
|
}
|
|
}
|
|
|
|
// if parameter isn't passed, then it is passed into some outer lambda or it is a local variable,
|
|
// so it should be loaded from closure or from the locals. Then the closure is null will be an invalid state.
|
|
// if it's a local variable, then store the right value in it
|
|
var localVariableIdx = closure.GetDefinedLocalVarOrDefault(leftParamExpr);
|
|
if (localVariableIdx != -1)
|
|
{
|
|
if (!TryEmit(right, paramExprs, il, ref closure, flags))
|
|
return false;
|
|
|
|
if ((right as ParameterExpression)?.IsByRef == true)
|
|
il.Emit(OpCodes.Ldind_I4);
|
|
|
|
if ((parent & ParentFlags.IgnoreResult) == 0) // if we have to push the result back, duplicate the right value
|
|
il.Emit(OpCodes.Dup);
|
|
|
|
EmitStoreLocalVariable(il, localVariableIdx);
|
|
return true;
|
|
}
|
|
|
|
// check that it's a captured parameter by closure
|
|
var nonPassedParams = closure.NonPassedParameters;
|
|
var nonPassedParamIndex = nonPassedParams.Length - 1;
|
|
while (nonPassedParamIndex != -1 &&
|
|
!ReferenceEquals(nonPassedParams[nonPassedParamIndex], leftParamExpr))
|
|
--nonPassedParamIndex;
|
|
if (nonPassedParamIndex == -1)
|
|
return false; // what??? no chance
|
|
|
|
il.Emit(OpCodes.Ldarg_0); // closure is always a first argument
|
|
|
|
if ((parent & ParentFlags.IgnoreResult) == 0)
|
|
{
|
|
if (!TryEmit(right, paramExprs, il, ref closure, flags))
|
|
return false;
|
|
|
|
var valueVarIndex = il.GetNextLocalVarIndex(expr.Type); // store left value in variable
|
|
EmitStoreLocalVariable(il, valueVarIndex);
|
|
|
|
// load array field and param item index
|
|
il.Emit(OpCodes.Ldfld, ArrayClosureWithNonPassedParamsField);
|
|
EmitLoadConstantInt(il, nonPassedParamIndex);
|
|
EmitLoadLocalVariable(il, valueVarIndex);
|
|
if (expr.Type.IsValueType())
|
|
il.Emit(OpCodes.Box, expr.Type);
|
|
il.Emit(OpCodes.Stelem_Ref); // put the variable into array
|
|
EmitLoadLocalVariable(il, valueVarIndex);
|
|
}
|
|
else
|
|
{
|
|
// load array field and param item index
|
|
il.Emit(OpCodes.Ldfld, ArrayClosureWithNonPassedParamsField);
|
|
EmitLoadConstantInt(il, nonPassedParamIndex);
|
|
|
|
if (!TryEmit(right, paramExprs, il, ref closure, flags))
|
|
return false;
|
|
|
|
if (expr.Type.IsValueType())
|
|
il.Emit(OpCodes.Box, expr.Type);
|
|
il.Emit(OpCodes.Stelem_Ref); // put the variable into array
|
|
}
|
|
|
|
return true;
|
|
|
|
case ExpressionType.MemberAccess:
|
|
var assignFromLocalVar = right.NodeType == ExpressionType.Try;
|
|
|
|
var resultLocalVarIndex = -1;
|
|
if (assignFromLocalVar)
|
|
{
|
|
resultLocalVarIndex = il.GetNextLocalVarIndex(right.Type);
|
|
|
|
if (!TryEmit(right, paramExprs, il, ref closure, ParentFlags.Empty))
|
|
return false;
|
|
|
|
EmitStoreLocalVariable(il, resultLocalVarIndex);
|
|
}
|
|
|
|
var memberExpr = (MemberExpression)left;
|
|
var objExpr = memberExpr.Expression;
|
|
if (objExpr != null &&
|
|
!TryEmit(objExpr, paramExprs, il, ref closure, flags | ParentFlags.MemberAccess | ParentFlags.InstanceAccess))
|
|
return false;
|
|
|
|
if (assignFromLocalVar)
|
|
EmitLoadLocalVariable(il, resultLocalVarIndex);
|
|
else if (!TryEmit(right, paramExprs, il, ref closure, ParentFlags.Empty))
|
|
return false;
|
|
|
|
var member = memberExpr.Member;
|
|
if ((parent & ParentFlags.IgnoreResult) != 0)
|
|
return EmitMemberAssign(il, member);
|
|
|
|
il.Emit(OpCodes.Dup);
|
|
|
|
var rightVarIndex = il.GetNextLocalVarIndex(expr.Type); // store right value in variable
|
|
EmitStoreLocalVariable(il, rightVarIndex);
|
|
|
|
if (!EmitMemberAssign(il, member))
|
|
return false;
|
|
|
|
EmitLoadLocalVariable(il, rightVarIndex);
|
|
return true;
|
|
|
|
case ExpressionType.Index:
|
|
var indexExpr = (IndexExpression)left;
|
|
|
|
var obj = indexExpr.Object;
|
|
if (obj != null && !TryEmit(obj, paramExprs, il, ref closure, flags))
|
|
return false;
|
|
|
|
var indexArgExprs = indexExpr.Arguments;
|
|
for (var i = 0; i < indexArgExprs.Count; i++)
|
|
if (!TryEmit(indexArgExprs[i], paramExprs, il, ref closure, flags, i))
|
|
return false;
|
|
|
|
if (!TryEmit(right, paramExprs, il, ref closure, flags))
|
|
return false;
|
|
|
|
if ((parent & ParentFlags.IgnoreResult) != 0)
|
|
return TryEmitIndexAssign(indexExpr, obj?.Type, expr.Type, il);
|
|
|
|
var varIndex = il.GetNextLocalVarIndex(expr.Type); // store value in variable to return
|
|
il.Emit(OpCodes.Dup);
|
|
EmitStoreLocalVariable(il, varIndex);
|
|
|
|
if (!TryEmitIndexAssign(indexExpr, obj?.Type, expr.Type, il))
|
|
return false;
|
|
|
|
EmitLoadLocalVariable(il, varIndex);
|
|
return true;
|
|
|
|
default: // todo: not yet support assignment targets
|
|
return false;
|
|
}
|
|
}
|
|
|
|
private static void EmitByRefStore(ILGenerator il, Type type)
|
|
{
|
|
if (type == typeof(int) || type == typeof(uint))
|
|
il.Emit(OpCodes.Stind_I4);
|
|
else if (type == typeof(byte))
|
|
il.Emit(OpCodes.Stind_I1);
|
|
else if (type == typeof(short) || type == typeof(ushort))
|
|
il.Emit(OpCodes.Stind_I2);
|
|
else if (type == typeof(long) || type == typeof(ulong))
|
|
il.Emit(OpCodes.Stind_I8);
|
|
else if (type == typeof(float))
|
|
il.Emit(OpCodes.Stind_R4);
|
|
else if (type == typeof(double))
|
|
il.Emit(OpCodes.Stind_R8);
|
|
else if (type == typeof(object))
|
|
il.Emit(OpCodes.Stind_Ref);
|
|
else if (type == typeof(IntPtr) || type == typeof(UIntPtr))
|
|
il.Emit(OpCodes.Stind_I);
|
|
else
|
|
il.Emit(OpCodes.Stobj, type);
|
|
}
|
|
|
|
private static bool TryEmitIndexAssign(IndexExpression indexExpr, Type instType, Type elementType, ILGenerator il)
|
|
{
|
|
if (indexExpr.Indexer != null)
|
|
return EmitMemberAssign(il, indexExpr.Indexer);
|
|
|
|
if (indexExpr.Arguments.Count == 1) // one dimensional array
|
|
{
|
|
if (elementType.IsValueType())
|
|
il.Emit(OpCodes.Stelem, elementType);
|
|
else
|
|
il.Emit(OpCodes.Stelem_Ref);
|
|
return true;
|
|
}
|
|
|
|
// multi dimensional array
|
|
return EmitMethodCall(il, instType?.FindMethod("Set"));
|
|
}
|
|
|
|
private static bool TryEmitMethodCall(Expression expr,
|
|
IReadOnlyList<ParameterExpression> paramExprs, ILGenerator il, ref ClosureInfo closure, ParentFlags parent)
|
|
{
|
|
var flags = parent & ~ParentFlags.IgnoreResult | ParentFlags.Call;
|
|
var callExpr = (MethodCallExpression)expr;
|
|
var objExpr = callExpr.Object;
|
|
var method = callExpr.Method;
|
|
var methodParams = method.GetParameters();
|
|
var objIsValueType = false;
|
|
if (objExpr != null)
|
|
{
|
|
if (!TryEmit(objExpr, paramExprs, il, ref closure, flags | ParentFlags.InstanceAccess))
|
|
return false;
|
|
|
|
objIsValueType = objExpr.Type.IsValueType();
|
|
if (objIsValueType && objExpr.NodeType != ExpressionType.Parameter && !closure.LastEmitIsAddress)
|
|
EmitStoreLocalVariableAndLoadItsAddress(il, objExpr.Type);
|
|
}
|
|
|
|
var fewArgCount = callExpr.FewArgumentCount;
|
|
if (fewArgCount >= 0)
|
|
{
|
|
if (fewArgCount == 1)
|
|
{
|
|
if (!TryEmit(((OneArgumentMethodCallExpression)callExpr).Argument, paramExprs, il, ref closure, flags, methodParams[0].ParameterType.IsByRef ? 0 : -1))
|
|
return false;
|
|
}
|
|
else if (fewArgCount == 2)
|
|
{
|
|
var twoArgsExpr = (TwoArgumentsMethodCallExpression)callExpr;
|
|
if (!TryEmit(twoArgsExpr.Argument0, paramExprs, il, ref closure, flags, methodParams[0].ParameterType.IsByRef ? 0 : -1) ||
|
|
!TryEmit(twoArgsExpr.Argument1, paramExprs, il, ref closure, flags, methodParams[1].ParameterType.IsByRef ? 1 : -1))
|
|
return false;
|
|
}
|
|
else if (fewArgCount == 3)
|
|
{
|
|
var threeArgsExpr = (ThreeArgumentsMethodCallExpression)callExpr;
|
|
if (!TryEmit(threeArgsExpr.Argument0, paramExprs, il, ref closure, flags, methodParams[0].ParameterType.IsByRef ? 0 : -1) ||
|
|
!TryEmit(threeArgsExpr.Argument1, paramExprs, il, ref closure, flags, methodParams[1].ParameterType.IsByRef ? 1 : -1) ||
|
|
!TryEmit(threeArgsExpr.Argument2, paramExprs, il, ref closure, flags, methodParams[2].ParameterType.IsByRef ? 2 : -1))
|
|
return false;
|
|
}
|
|
else if (fewArgCount == 4)
|
|
{
|
|
var fourArgsExpr = (FourArgumentsMethodCallExpression)callExpr;
|
|
if (!TryEmit(fourArgsExpr.Argument0, paramExprs, il, ref closure, flags, methodParams[0].ParameterType.IsByRef ? 0 : -1) ||
|
|
!TryEmit(fourArgsExpr.Argument1, paramExprs, il, ref closure, flags, methodParams[1].ParameterType.IsByRef ? 1 : -1) ||
|
|
!TryEmit(fourArgsExpr.Argument2, paramExprs, il, ref closure, flags, methodParams[2].ParameterType.IsByRef ? 2 : -1) ||
|
|
!TryEmit(fourArgsExpr.Argument3, paramExprs, il, ref closure, flags, methodParams[3].ParameterType.IsByRef ? 3 : -1))
|
|
return false;
|
|
}
|
|
else if (fewArgCount == 5)
|
|
{
|
|
var fiveArgsExpr = (FiveArgumentsMethodCallExpression)callExpr;
|
|
if (!TryEmit(fiveArgsExpr.Argument0, paramExprs, il, ref closure, flags, methodParams[0].ParameterType.IsByRef ? 0 : -1) ||
|
|
!TryEmit(fiveArgsExpr.Argument1, paramExprs, il, ref closure, flags, methodParams[1].ParameterType.IsByRef ? 1 : -1) ||
|
|
!TryEmit(fiveArgsExpr.Argument2, paramExprs, il, ref closure, flags, methodParams[2].ParameterType.IsByRef ? 2 : -1) ||
|
|
!TryEmit(fiveArgsExpr.Argument3, paramExprs, il, ref closure, flags, methodParams[3].ParameterType.IsByRef ? 3 : -1) ||
|
|
!TryEmit(fiveArgsExpr.Argument4, paramExprs, il, ref closure, flags, methodParams[4].ParameterType.IsByRef ? 4 : -1))
|
|
return false;
|
|
}
|
|
|
|
if (objIsValueType && method.IsVirtual)
|
|
il.Emit(OpCodes.Constrained, objExpr.Type);
|
|
il.Emit(method.IsVirtual ? OpCodes.Callvirt : OpCodes.Call, method);
|
|
if (parent.IgnoresResult() && method.ReturnType != typeof(void))
|
|
il.Emit(OpCodes.Pop);
|
|
closure.LastEmitIsAddress = false;
|
|
return true;
|
|
}
|
|
|
|
var args = callExpr.Arguments;
|
|
for (var i = 0; i < methodParams.Length; i++)
|
|
if (!TryEmit(args[i], paramExprs, il, ref closure, flags, methodParams[i].ParameterType.IsByRef ? i : -1))
|
|
return false;
|
|
|
|
if (objIsValueType && method.IsVirtual)
|
|
il.Emit(OpCodes.Constrained, objExpr.Type);
|
|
il.Emit(method.IsVirtual ? OpCodes.Callvirt : OpCodes.Call, method);
|
|
if (parent.IgnoresResult() && method.ReturnType != typeof(void))
|
|
il.Emit(OpCodes.Pop);
|
|
|
|
closure.LastEmitIsAddress = false;
|
|
return true;
|
|
}
|
|
|
|
private static bool TryEmitMemberAccess(MemberExpression expr,
|
|
IReadOnlyList<ParameterExpression> paramExprs, ILGenerator il, ref ClosureInfo closure, ParentFlags parent)
|
|
{
|
|
if (expr.Member is PropertyInfo prop)
|
|
{
|
|
var instanceExpr = expr.Expression;
|
|
if (instanceExpr != null)
|
|
{
|
|
if (!TryEmit(instanceExpr, paramExprs, il, ref closure,
|
|
~ParentFlags.IgnoreResult & ~ParentFlags.DupMemberOwner &
|
|
(parent | ParentFlags.Call | ParentFlags.MemberAccess | ParentFlags.InstanceAccess)))
|
|
return false;
|
|
|
|
if ((parent & ParentFlags.DupMemberOwner) != 0)
|
|
il.Emit(OpCodes.Dup);
|
|
|
|
// Value type special treatment to load address of value instance in order to access a field or call a method.
|
|
// Parameter should be excluded because it already loads an address via `LDARGA`, and you don't need to.
|
|
// And for field access no need to load address, cause the field stored on stack nearby
|
|
if (!closure.LastEmitIsAddress &&
|
|
instanceExpr.NodeType != ExpressionType.Parameter && instanceExpr.Type.IsValueType())
|
|
EmitStoreLocalVariableAndLoadItsAddress(il, instanceExpr.Type);
|
|
}
|
|
|
|
closure.LastEmitIsAddress = false;
|
|
var propGetter = prop.DeclaringType.FindPropertyGetMethod(prop.Name);
|
|
if (propGetter == null)
|
|
return false;
|
|
|
|
il.Emit(propGetter.IsVirtual ? OpCodes.Callvirt : OpCodes.Call, propGetter);
|
|
return true;
|
|
}
|
|
|
|
if (expr.Member is FieldInfo field)
|
|
{
|
|
var instanceExpr = expr.Expression;
|
|
if (instanceExpr != null)
|
|
{
|
|
if (!TryEmit(instanceExpr, paramExprs, il, ref closure,
|
|
~ParentFlags.IgnoreResult & ~ParentFlags.DupMemberOwner &
|
|
(parent | ParentFlags.MemberAccess | ParentFlags.InstanceAccess)))
|
|
return false;
|
|
|
|
if ((parent & ParentFlags.DupMemberOwner) != 0)
|
|
il.Emit(OpCodes.Dup);
|
|
|
|
closure.LastEmitIsAddress = field.FieldType.IsValueType() && (parent & ParentFlags.InstanceAccess) != 0;
|
|
il.Emit(closure.LastEmitIsAddress ? OpCodes.Ldflda : OpCodes.Ldfld, field);
|
|
}
|
|
else if (field.IsLiteral)
|
|
{
|
|
var fieldValue = field.GetValue(null);
|
|
if (fieldValue != null)
|
|
return TryEmitNotNullConstant(false, field.FieldType, fieldValue, il, ref closure);
|
|
|
|
il.Emit(OpCodes.Ldnull);
|
|
}
|
|
else
|
|
{
|
|
il.Emit(OpCodes.Ldsfld, field);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
// ReSharper disable once FunctionComplexityOverflow
|
|
private static bool TryEmitNestedLambda(LambdaExpression lambdaExpr,
|
|
IReadOnlyList<ParameterExpression> outerParamExprs, ILGenerator il, ref ClosureInfo closure)
|
|
{
|
|
// First, find in closed compiled lambdas the one corresponding to the current lambda expression.
|
|
// Situation with not found lambda is not possible/exceptional,
|
|
// it means that we somehow skipped the lambda expression while collecting closure info.
|
|
var outerNestedLambdas = closure.NestedLambdas;
|
|
var outerNestedLambdaIndex = outerNestedLambdas.Length - 1;
|
|
while (outerNestedLambdaIndex != -1 &&
|
|
!ReferenceEquals(outerNestedLambdas[outerNestedLambdaIndex].LambdaExpression, lambdaExpr))
|
|
--outerNestedLambdaIndex;
|
|
if (outerNestedLambdaIndex == -1)
|
|
return false;
|
|
|
|
var nestedLambdaInfo = closure.NestedLambdas[outerNestedLambdaIndex];
|
|
var nestedLambda = nestedLambdaInfo.Lambda;
|
|
var nestedLambdaInClosureIndex = outerNestedLambdaIndex + closure.Constants.Count;
|
|
|
|
var varIndex = nestedLambdaInfo.UsageCountOrVarIndex - 1;
|
|
if (varIndex > 0)
|
|
EmitLoadLocalVariable(il, varIndex);
|
|
else
|
|
{
|
|
il.Emit(OpCodes.Ldloc_0);
|
|
EmitLoadConstantInt(il, nestedLambdaInClosureIndex);
|
|
il.Emit(OpCodes.Ldelem_Ref); // load the array item object
|
|
}
|
|
|
|
// If lambda does not use any outer parameters to be set in closure, then we're done
|
|
ref var nestedClosureInfo = ref nestedLambdaInfo.ClosureInfo;
|
|
var nestedNonPassedParams = nestedClosureInfo.NonPassedParameters;
|
|
if (nestedNonPassedParams.Length == 0)
|
|
return true;
|
|
|
|
//-------------------------------------------------------------------
|
|
// For the lambda with non-passed parameters (or variables) in closure
|
|
// we have loaded `NestedLambdaWithConstantsAndNestedLambdas` pair.
|
|
if (varIndex > 0)
|
|
{
|
|
// we are already have variable loaded
|
|
il.Emit(OpCodes.Ldfld, NestedLambdaWithConstantsAndNestedLambdas.NestedLambdaField);
|
|
EmitLoadLocalVariable(il, varIndex); // load the variable for the second time
|
|
il.Emit(OpCodes.Ldfld, NestedLambdaWithConstantsAndNestedLambdas.ConstantsAndNestedLambdasField);
|
|
}
|
|
else
|
|
{
|
|
var nestedLambdaAndClosureItemsVarIndex = il.GetNextLocalVarIndex(typeof(NestedLambdaWithConstantsAndNestedLambdas));
|
|
EmitStoreLocalVariable(il, nestedLambdaAndClosureItemsVarIndex);
|
|
|
|
// - load the `NestedLambda` field
|
|
EmitLoadLocalVariable(il, nestedLambdaAndClosureItemsVarIndex);
|
|
il.Emit(OpCodes.Ldfld, NestedLambdaWithConstantsAndNestedLambdas.NestedLambdaField);
|
|
|
|
// - load the `ConstantsAndNestedLambdas` field
|
|
EmitLoadLocalVariable(il, nestedLambdaAndClosureItemsVarIndex);
|
|
il.Emit(OpCodes.Ldfld, NestedLambdaWithConstantsAndNestedLambdas.ConstantsAndNestedLambdasField);
|
|
}
|
|
|
|
// - create `NonPassedParameters` array
|
|
EmitLoadConstantInt(il, nestedNonPassedParams.Length); // size of array
|
|
il.Emit(OpCodes.Newarr, typeof(object));
|
|
|
|
// - populate the `NonPassedParameters` array
|
|
var outerNonPassedParams = closure.NonPassedParameters;
|
|
for (var nestedParamIndex = 0; nestedParamIndex < nestedNonPassedParams.Length; ++nestedParamIndex)
|
|
{
|
|
var nestedParam = nestedNonPassedParams[nestedParamIndex];
|
|
|
|
// Duplicate nested array on stack to store the item, and load index to where to store
|
|
il.Emit(OpCodes.Dup);
|
|
EmitLoadConstantInt(il, nestedParamIndex);
|
|
|
|
var outerParamIndex = outerParamExprs.Count - 1;
|
|
while (outerParamIndex != -1 && !ReferenceEquals(outerParamExprs[outerParamIndex], nestedParam))
|
|
--outerParamIndex;
|
|
if (outerParamIndex != -1) // load parameter from input outer params
|
|
{
|
|
// Add `+1` to index because the `0` index is for the closure argument
|
|
if (outerParamIndex == 0)
|
|
il.Emit(OpCodes.Ldarg_1);
|
|
else if (outerParamIndex == 1)
|
|
il.Emit(OpCodes.Ldarg_2);
|
|
else if (outerParamIndex == 2)
|
|
il.Emit(OpCodes.Ldarg_3);
|
|
else
|
|
il.Emit(OpCodes.Ldarg_S, (byte)(1 + outerParamIndex));
|
|
|
|
if (nestedParam.Type.IsValueType())
|
|
il.Emit(OpCodes.Box, nestedParam.Type);
|
|
}
|
|
else // load parameter from outer closure or from the local variables
|
|
{
|
|
if (outerNonPassedParams.Length == 0)
|
|
return false; // impossible, better to throw?
|
|
|
|
var variableIdx = closure.GetDefinedLocalVarOrDefault(nestedParam);
|
|
if (variableIdx != -1) // it's a local variable
|
|
{
|
|
EmitLoadLocalVariable(il, variableIdx);
|
|
}
|
|
else // it's a parameter from the outer closure
|
|
{
|
|
var outerNonPassedParamIndex = outerNonPassedParams.Length - 1;
|
|
while (outerNonPassedParamIndex != -1 && !ReferenceEquals(outerNonPassedParams[outerNonPassedParamIndex], nestedParam))
|
|
--outerNonPassedParamIndex;
|
|
if (outerNonPassedParamIndex == -1)
|
|
return false; // impossible
|
|
|
|
// Load the parameter from outer closure `Items` array
|
|
il.Emit(OpCodes.Ldarg_0); // closure is always a first argument
|
|
il.Emit(OpCodes.Ldfld, ArrayClosureWithNonPassedParamsField);
|
|
EmitLoadConstantInt(il, outerNonPassedParamIndex);
|
|
il.Emit(OpCodes.Ldelem_Ref);
|
|
}
|
|
}
|
|
|
|
// Store the item into nested lambda array
|
|
il.Emit(OpCodes.Stelem_Ref);
|
|
}
|
|
|
|
// - create `ArrayClosureWithNonPassedParams` out of the both above
|
|
il.Emit(OpCodes.Newobj, ArrayClosureWithNonPassedParamsConstructor);
|
|
|
|
// - call `Curry` method with nested lambda and array closure to produce a closed lambda with the expected signature
|
|
var lambdaTypeArgs = nestedLambda.GetType().GetTypeInfo().GenericTypeArguments;
|
|
|
|
var closureMethod = nestedLambdaInfo.LambdaExpression.ReturnType == typeof(void)
|
|
? CurryClosureActions.Methods[lambdaTypeArgs.Length - 1].MakeGenericMethod(lambdaTypeArgs)
|
|
: CurryClosureFuncs.Methods[lambdaTypeArgs.Length - 2].MakeGenericMethod(lambdaTypeArgs);
|
|
|
|
il.Emit(OpCodes.Call, closureMethod);
|
|
return true;
|
|
}
|
|
|
|
private static bool TryEmitInvoke(InvocationExpression expr,
|
|
IReadOnlyList<ParameterExpression> paramExprs, ILGenerator il, ref ClosureInfo closure, ParentFlags parent)
|
|
{
|
|
var lambda = expr.Expression;
|
|
if (!TryEmit(lambda, paramExprs, il, ref closure, parent & ~ParentFlags.IgnoreResult))
|
|
return false;
|
|
|
|
var argExprs = expr.Arguments;
|
|
for (var i = 0; i < argExprs.Count; i++)
|
|
if (!TryEmit(argExprs[i], paramExprs, il, ref closure,
|
|
parent & ~ParentFlags.IgnoreResult & ~ParentFlags.InstanceAccess,
|
|
argExprs[i].Type.IsByRef ? i : -1))
|
|
return false;
|
|
|
|
var delegateInvokeMethod = lambda.Type.FindDelegateInvokeMethod();
|
|
il.Emit(OpCodes.Call, delegateInvokeMethod);
|
|
|
|
if ((parent & ParentFlags.IgnoreResult) != 0 && delegateInvokeMethod.ReturnType != typeof(void))
|
|
il.Emit(OpCodes.Pop);
|
|
|
|
return true;
|
|
}
|
|
|
|
private static bool TryEmitSwitch(SwitchExpression expr,
|
|
IReadOnlyList<ParameterExpression> paramExprs, ILGenerator il, ref ClosureInfo closure, ParentFlags parent)
|
|
{
|
|
// todo:
|
|
//- use switch statement for int comparison (if int difference is less or equal 3 -> use IL switch)
|
|
//- TryEmitComparison should not emit "CEQ" so we could use Beq_S instead of Brtrue_S (not always possible (nullable))
|
|
//- if switch SwitchValue is a nullable parameter, we should call getValue only once and store the result.
|
|
//- use comparison methods (when defined)
|
|
|
|
var endLabel = il.DefineLabel();
|
|
var labels = new Label[expr.Cases.Count];
|
|
for (var index = 0; index < expr.Cases.Count; index++)
|
|
{
|
|
var switchCase = expr.Cases[index];
|
|
labels[index] = il.DefineLabel();
|
|
|
|
foreach (var switchCaseTestValue in switchCase.TestValues)
|
|
{
|
|
if (!TryEmitComparison(expr.SwitchValue, switchCaseTestValue, ExpressionType.Equal, paramExprs, il,
|
|
ref closure, parent))
|
|
return false;
|
|
il.Emit(OpCodes.Brtrue, labels[index]);
|
|
}
|
|
}
|
|
|
|
if (expr.DefaultBody != null)
|
|
{
|
|
if (!TryEmit(expr.DefaultBody, paramExprs, il, ref closure, parent))
|
|
return false;
|
|
il.Emit(OpCodes.Br, endLabel);
|
|
}
|
|
|
|
for (var index = 0; index < expr.Cases.Count; index++)
|
|
{
|
|
var switchCase = expr.Cases[index];
|
|
il.MarkLabel(labels[index]);
|
|
if (!TryEmit(switchCase.Body, paramExprs, il, ref closure, parent))
|
|
return false;
|
|
|
|
if (index != expr.Cases.Count - 1)
|
|
il.Emit(OpCodes.Br, endLabel);
|
|
}
|
|
|
|
il.MarkLabel(endLabel);
|
|
|
|
return true;
|
|
}
|
|
|
|
private static bool TryEmitComparison(Expression exprLeft, Expression exprRight, ExpressionType expressionType,
|
|
IReadOnlyList<ParameterExpression> paramExprs, ILGenerator il, ref ClosureInfo closure, ParentFlags parent)
|
|
{
|
|
var leftOpType = exprLeft.Type;
|
|
var leftIsNullable = leftOpType.IsNullable();
|
|
var rightOpType = exprRight.Type;
|
|
if (exprRight is ConstantExpression c && c.Value == null && exprRight.Type == typeof(object))
|
|
rightOpType = leftOpType;
|
|
|
|
int lVarIndex = -1, rVarIndex = -1;
|
|
if (!TryEmit(exprLeft, paramExprs, il, ref closure, parent & ~ParentFlags.IgnoreResult & ~ParentFlags.InstanceAccess))
|
|
return false;
|
|
|
|
if (leftIsNullable)
|
|
{
|
|
lVarIndex = EmitStoreLocalVariableAndLoadItsAddress(il, leftOpType);
|
|
il.Emit(OpCodes.Call, leftOpType.FindNullableGetValueOrDefaultMethod());
|
|
leftOpType = Nullable.GetUnderlyingType(leftOpType);
|
|
}
|
|
|
|
if (!TryEmit(exprRight, paramExprs, il, ref closure, parent & ~ParentFlags.IgnoreResult & ~ParentFlags.InstanceAccess))
|
|
return false;
|
|
|
|
if (leftOpType != rightOpType)
|
|
{
|
|
if (leftOpType.IsClass() && rightOpType.IsClass() &&
|
|
(leftOpType == typeof(object) || rightOpType == typeof(object)))
|
|
{
|
|
if (expressionType == ExpressionType.Equal)
|
|
{
|
|
il.Emit(OpCodes.Ceq);
|
|
if ((parent & ParentFlags.IgnoreResult) != 0)
|
|
il.Emit(OpCodes.Pop);
|
|
}
|
|
else if (expressionType == ExpressionType.NotEqual)
|
|
{
|
|
il.Emit(OpCodes.Ceq);
|
|
il.Emit(OpCodes.Ldc_I4_0);
|
|
il.Emit(OpCodes.Ceq);
|
|
}
|
|
else
|
|
return false;
|
|
|
|
if ((parent & ParentFlags.IgnoreResult) != 0)
|
|
il.Emit(OpCodes.Pop);
|
|
|
|
return true;
|
|
}
|
|
}
|
|
|
|
if (rightOpType.IsNullable())
|
|
{
|
|
rVarIndex = EmitStoreLocalVariableAndLoadItsAddress(il, rightOpType);
|
|
il.Emit(OpCodes.Call, rightOpType.FindNullableGetValueOrDefaultMethod());
|
|
// ReSharper disable once AssignNullToNotNullAttribute
|
|
rightOpType = Nullable.GetUnderlyingType(rightOpType);
|
|
}
|
|
|
|
var leftOpTypeInfo = leftOpType.GetTypeInfo();
|
|
if (!leftOpTypeInfo.IsPrimitive && !leftOpTypeInfo.IsEnum)
|
|
{
|
|
var methodName
|
|
= expressionType == ExpressionType.Equal ? "op_Equality"
|
|
: expressionType == ExpressionType.NotEqual ? "op_Inequality"
|
|
: expressionType == ExpressionType.GreaterThan ? "op_GreaterThan"
|
|
: expressionType == ExpressionType.GreaterThanOrEqual ? "op_GreaterThanOrEqual"
|
|
: expressionType == ExpressionType.LessThan ? "op_LessThan"
|
|
: expressionType == ExpressionType.LessThanOrEqual ? "op_LessThanOrEqual"
|
|
: null;
|
|
|
|
if (methodName == null)
|
|
return false;
|
|
|
|
// todo: for now handling only parameters of the same type
|
|
var methods = leftOpTypeInfo.DeclaredMethods.AsArray();
|
|
for (var i = 0; i < methods.Length; i++)
|
|
{
|
|
var m = methods[i];
|
|
if (m.IsSpecialName && m.IsStatic && m.Name == methodName &&
|
|
IsComparisonOperatorSignature(leftOpType, m.GetParameters()))
|
|
{
|
|
il.Emit(OpCodes.Call, m);
|
|
return true;
|
|
}
|
|
}
|
|
|
|
if (expressionType != ExpressionType.Equal && expressionType != ExpressionType.NotEqual)
|
|
return false;
|
|
|
|
il.Emit(OpCodes.Call, _objectEqualsMethod);
|
|
|
|
if (expressionType == ExpressionType.NotEqual) // invert result for not equal
|
|
{
|
|
il.Emit(OpCodes.Ldc_I4_0);
|
|
il.Emit(OpCodes.Ceq);
|
|
}
|
|
|
|
if (leftIsNullable)
|
|
goto nullCheck;
|
|
|
|
if ((parent & ParentFlags.IgnoreResult) > 0)
|
|
il.Emit(OpCodes.Pop);
|
|
|
|
return true;
|
|
}
|
|
|
|
// handle primitives comparison
|
|
switch (expressionType)
|
|
{
|
|
case ExpressionType.Equal:
|
|
il.Emit(OpCodes.Ceq);
|
|
break;
|
|
|
|
case ExpressionType.NotEqual:
|
|
il.Emit(OpCodes.Ceq);
|
|
il.Emit(OpCodes.Ldc_I4_0);
|
|
il.Emit(OpCodes.Ceq);
|
|
break;
|
|
|
|
case ExpressionType.LessThan:
|
|
il.Emit(OpCodes.Clt);
|
|
break;
|
|
|
|
case ExpressionType.GreaterThan:
|
|
il.Emit(OpCodes.Cgt);
|
|
break;
|
|
|
|
case ExpressionType.GreaterThanOrEqual:
|
|
case ExpressionType.LessThanOrEqual:
|
|
var ifTrueLabel = il.DefineLabel();
|
|
if (rightOpType == typeof(uint) || rightOpType == typeof(ulong) ||
|
|
rightOpType == typeof(ushort) || rightOpType == typeof(byte))
|
|
il.Emit(expressionType == ExpressionType.GreaterThanOrEqual ? OpCodes.Bge_Un_S : OpCodes.Ble_Un_S, ifTrueLabel);
|
|
else
|
|
il.Emit(expressionType == ExpressionType.GreaterThanOrEqual ? OpCodes.Bge_S : OpCodes.Ble_S, ifTrueLabel);
|
|
|
|
il.Emit(OpCodes.Ldc_I4_0);
|
|
var doneLabel = il.DefineLabel();
|
|
il.Emit(OpCodes.Br_S, doneLabel);
|
|
|
|
il.MarkLabel(ifTrueLabel);
|
|
il.Emit(OpCodes.Ldc_I4_1);
|
|
|
|
il.MarkLabel(doneLabel);
|
|
break;
|
|
|
|
default:
|
|
return false;
|
|
}
|
|
|
|
nullCheck:
|
|
if (leftIsNullable)
|
|
{
|
|
var leftNullableHasValueGetterMethod = exprLeft.Type.FindNullableHasValueGetterMethod();
|
|
|
|
EmitLoadLocalVariableAddress(il, lVarIndex);
|
|
il.Emit(OpCodes.Call, leftNullableHasValueGetterMethod);
|
|
|
|
// ReSharper disable once AssignNullToNotNullAttribute
|
|
EmitLoadLocalVariableAddress(il, rVarIndex);
|
|
il.Emit(OpCodes.Call, leftNullableHasValueGetterMethod);
|
|
|
|
switch (expressionType)
|
|
{
|
|
case ExpressionType.Equal:
|
|
il.Emit(OpCodes.Ceq); // compare both HasValue calls
|
|
il.Emit(OpCodes.And); // both results need to be true
|
|
break;
|
|
|
|
case ExpressionType.NotEqual:
|
|
il.Emit(OpCodes.Ceq);
|
|
il.Emit(OpCodes.Ldc_I4_0);
|
|
il.Emit(OpCodes.Ceq);
|
|
il.Emit(OpCodes.Or);
|
|
break;
|
|
|
|
case ExpressionType.LessThan:
|
|
case ExpressionType.GreaterThan:
|
|
case ExpressionType.LessThanOrEqual:
|
|
case ExpressionType.GreaterThanOrEqual:
|
|
il.Emit(OpCodes.Ceq);
|
|
il.Emit(OpCodes.Ldc_I4_1);
|
|
il.Emit(OpCodes.Ceq);
|
|
il.Emit(OpCodes.And);
|
|
break;
|
|
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if ((parent & ParentFlags.IgnoreResult) > 0)
|
|
il.Emit(OpCodes.Pop);
|
|
|
|
return true;
|
|
}
|
|
|
|
private static bool IsComparisonOperatorSignature(Type t, ParameterInfo[] pars) =>
|
|
pars.Length == 2 && pars[0].ParameterType == t && pars[1].ParameterType == t;
|
|
|
|
private static bool TryEmitArithmetic(BinaryExpression expr, ExpressionType exprNodeType,
|
|
IReadOnlyList<ParameterExpression> paramExprs, ILGenerator il, ref ClosureInfo closure,
|
|
ParentFlags parent)
|
|
{
|
|
var flags = parent & ~ParentFlags.IgnoreResult & ~ParentFlags.InstanceCall | ParentFlags.Arithmetic;
|
|
|
|
var leftNoValueLabel = default(Label);
|
|
var leftExpr = expr.Left;
|
|
var lefType = leftExpr.Type;
|
|
var leftIsNullable = lefType.IsNullable();
|
|
if (leftIsNullable)
|
|
{
|
|
leftNoValueLabel = il.DefineLabel();
|
|
if (!TryEmit(leftExpr, paramExprs, il, ref closure, flags | ParentFlags.InstanceCall))
|
|
return false;
|
|
|
|
if (!closure.LastEmitIsAddress)
|
|
EmitStoreLocalVariableAndLoadItsAddress(il, lefType);
|
|
|
|
il.Emit(OpCodes.Dup);
|
|
il.Emit(OpCodes.Call, lefType.FindNullableHasValueGetterMethod());
|
|
|
|
il.Emit(OpCodes.Brfalse, leftNoValueLabel);
|
|
il.Emit(OpCodes.Call, lefType.FindNullableGetValueOrDefaultMethod());
|
|
}
|
|
else if (!TryEmit(leftExpr, paramExprs, il, ref closure, flags))
|
|
return false;
|
|
|
|
var rightNoValueLabel = default(Label);
|
|
var rightExpr = expr.Right;
|
|
var rightType = rightExpr.Type;
|
|
var rightIsNullable = rightType.IsNullable();
|
|
if (rightIsNullable)
|
|
{
|
|
rightNoValueLabel = il.DefineLabel();
|
|
if (!TryEmit(rightExpr, paramExprs, il, ref closure, flags | ParentFlags.InstanceCall))
|
|
return false;
|
|
|
|
if (!closure.LastEmitIsAddress)
|
|
EmitStoreLocalVariableAndLoadItsAddress(il, rightType);
|
|
|
|
il.Emit(OpCodes.Dup);
|
|
il.Emit(OpCodes.Call, rightType.FindNullableHasValueGetterMethod());
|
|
il.Emit(OpCodes.Brfalse, rightNoValueLabel);
|
|
il.Emit(OpCodes.Call, rightType.FindNullableGetValueOrDefaultMethod());
|
|
}
|
|
else if (!TryEmit(rightExpr, paramExprs, il, ref closure, flags))
|
|
return false;
|
|
|
|
var exprType = expr.Type;
|
|
if (!TryEmitArithmeticOperation(expr, exprNodeType, exprType, il))
|
|
return false;
|
|
|
|
if (leftIsNullable || rightIsNullable)
|
|
{
|
|
var valueLabel = il.DefineLabel();
|
|
il.Emit(OpCodes.Br, valueLabel);
|
|
|
|
if (rightIsNullable)
|
|
il.MarkLabel(rightNoValueLabel);
|
|
il.Emit(OpCodes.Pop);
|
|
|
|
if (leftIsNullable)
|
|
il.MarkLabel(leftNoValueLabel);
|
|
il.Emit(OpCodes.Pop);
|
|
|
|
if (exprType.IsNullable())
|
|
{
|
|
var endL = il.DefineLabel();
|
|
var locIndex = InitValueTypeVariable(il, exprType);
|
|
EmitLoadLocalVariable(il, locIndex);
|
|
il.Emit(OpCodes.Br_S, endL);
|
|
il.MarkLabel(valueLabel);
|
|
il.Emit(OpCodes.Newobj, exprType.GetTypeInfo().DeclaredConstructors.GetFirst());
|
|
il.MarkLabel(endL);
|
|
}
|
|
else
|
|
{
|
|
il.Emit(OpCodes.Ldc_I4_0);
|
|
il.MarkLabel(valueLabel);
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
private static bool TryEmitArithmeticOperation(BinaryExpression expr,
|
|
ExpressionType exprNodeType, Type exprType, ILGenerator il)
|
|
{
|
|
if (!exprType.IsPrimitive())
|
|
{
|
|
if (exprType.IsNullable())
|
|
exprType = Nullable.GetUnderlyingType(exprType);
|
|
|
|
if (!exprType.IsPrimitive())
|
|
{
|
|
MethodInfo method = null;
|
|
if (exprType == typeof(string))
|
|
{
|
|
var paraType = typeof(string);
|
|
if (expr.Left.Type != expr.Right.Type || expr.Left.Type != typeof(string))
|
|
paraType = typeof(object);
|
|
|
|
var methods = typeof(string).GetTypeInfo().DeclaredMethods.AsArray();
|
|
for (var i = 0; i < methods.Length; i++)
|
|
{
|
|
var m = methods[i];
|
|
if (m.IsStatic && m.Name == "Concat" &&
|
|
m.GetParameters().Length == 2 && m.GetParameters()[0].ParameterType == paraType)
|
|
{
|
|
method = m;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
var methodName
|
|
= exprNodeType == ExpressionType.Add ? "op_Addition"
|
|
: exprNodeType == ExpressionType.AddChecked ? "op_Addition"
|
|
: exprNodeType == ExpressionType.Subtract ? "op_Subtraction"
|
|
: exprNodeType == ExpressionType.SubtractChecked ? "op_Subtraction"
|
|
: exprNodeType == ExpressionType.Multiply ? "op_Multiply"
|
|
: exprNodeType == ExpressionType.MultiplyChecked ? "op_Multiply"
|
|
: exprNodeType == ExpressionType.Divide ? "op_Division"
|
|
: exprNodeType == ExpressionType.Modulo ? "op_Modulus"
|
|
: null;
|
|
|
|
if (methodName != null)
|
|
{
|
|
var methods = exprType.GetTypeInfo().DeclaredMethods.AsArray();
|
|
for (var i = 0; method == null && i < methods.Length; i++)
|
|
{
|
|
var m = methods[i];
|
|
if (m.IsSpecialName && m.IsStatic && m.Name == methodName)
|
|
method = m;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (method == null)
|
|
return false;
|
|
|
|
il.Emit(method.IsVirtual ? OpCodes.Callvirt : OpCodes.Call, method);
|
|
return true;
|
|
}
|
|
}
|
|
|
|
switch (exprNodeType)
|
|
{
|
|
case ExpressionType.Add:
|
|
case ExpressionType.AddAssign:
|
|
il.Emit(OpCodes.Add);
|
|
return true;
|
|
|
|
case ExpressionType.AddChecked:
|
|
case ExpressionType.AddAssignChecked:
|
|
il.Emit(exprType.IsUnsigned() ? OpCodes.Add_Ovf_Un : OpCodes.Add_Ovf);
|
|
return true;
|
|
|
|
case ExpressionType.Subtract:
|
|
case ExpressionType.SubtractAssign:
|
|
il.Emit(OpCodes.Sub);
|
|
return true;
|
|
|
|
case ExpressionType.SubtractChecked:
|
|
case ExpressionType.SubtractAssignChecked:
|
|
il.Emit(exprType.IsUnsigned() ? OpCodes.Sub_Ovf_Un : OpCodes.Sub_Ovf);
|
|
return true;
|
|
|
|
case ExpressionType.Multiply:
|
|
case ExpressionType.MultiplyAssign:
|
|
il.Emit(OpCodes.Mul);
|
|
return true;
|
|
|
|
case ExpressionType.MultiplyChecked:
|
|
case ExpressionType.MultiplyAssignChecked:
|
|
il.Emit(exprType.IsUnsigned() ? OpCodes.Mul_Ovf_Un : OpCodes.Mul_Ovf);
|
|
return true;
|
|
|
|
case ExpressionType.Divide:
|
|
case ExpressionType.DivideAssign:
|
|
il.Emit(OpCodes.Div);
|
|
return true;
|
|
|
|
case ExpressionType.Modulo:
|
|
case ExpressionType.ModuloAssign:
|
|
il.Emit(OpCodes.Rem);
|
|
return true;
|
|
|
|
case ExpressionType.And:
|
|
case ExpressionType.AndAssign:
|
|
il.Emit(OpCodes.And);
|
|
return true;
|
|
|
|
case ExpressionType.Or:
|
|
case ExpressionType.OrAssign:
|
|
il.Emit(OpCodes.Or);
|
|
return true;
|
|
|
|
case ExpressionType.ExclusiveOr:
|
|
case ExpressionType.ExclusiveOrAssign:
|
|
il.Emit(OpCodes.Xor);
|
|
return true;
|
|
|
|
case ExpressionType.LeftShift:
|
|
case ExpressionType.LeftShiftAssign:
|
|
il.Emit(OpCodes.Shl);
|
|
return true;
|
|
|
|
case ExpressionType.RightShift:
|
|
case ExpressionType.RightShiftAssign:
|
|
il.Emit(OpCodes.Shr);
|
|
return true;
|
|
|
|
case ExpressionType.Power:
|
|
il.Emit(OpCodes.Call, typeof(Math).FindMethod("Pow"));
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
private static bool TryEmitLogicalOperator(BinaryExpression expr,
|
|
IReadOnlyList<ParameterExpression> paramExprs, ILGenerator il, ref ClosureInfo closure, ParentFlags parent)
|
|
{
|
|
if (!TryEmit(expr.Left, paramExprs, il, ref closure, parent))
|
|
return false;
|
|
|
|
var labelSkipRight = il.DefineLabel();
|
|
il.Emit(expr.NodeType == ExpressionType.AndAlso ? OpCodes.Brfalse : OpCodes.Brtrue, labelSkipRight);
|
|
|
|
if (!TryEmit(expr.Right, paramExprs, il, ref closure, parent))
|
|
return false;
|
|
|
|
var labelDone = il.DefineLabel();
|
|
il.Emit(OpCodes.Br, labelDone);
|
|
|
|
il.MarkLabel(labelSkipRight); // label the second branch
|
|
il.Emit(expr.NodeType == ExpressionType.AndAlso ? OpCodes.Ldc_I4_0 : OpCodes.Ldc_I4_1);
|
|
il.MarkLabel(labelDone);
|
|
|
|
return true;
|
|
}
|
|
|
|
private static bool TryEmitConditional(ConditionalExpression expr,
|
|
IReadOnlyList<ParameterExpression> paramExprs, ILGenerator il, ref ClosureInfo closure, ParentFlags parent)
|
|
{
|
|
var testExpr = TryReduceCondition(expr.Test);
|
|
|
|
// detect a special simplistic case of comparison with `null`
|
|
var comparedWithNull = false;
|
|
if (testExpr is BinaryExpression b)
|
|
{
|
|
if (b.NodeType == ExpressionType.Equal || b.NodeType == ExpressionType.NotEqual ||
|
|
!b.Left.Type.IsNullable() && !b.Right.Type.IsNullable())
|
|
{
|
|
if (b.Right is ConstantExpression r && r.Value == null)
|
|
{
|
|
// the null comparison for nullable is actually a `nullable.HasValue` check,
|
|
// which implies member access on nullable struct - therefore loading it by address
|
|
if (b.Left.Type.IsNullable())
|
|
parent |= ParentFlags.MemberAccess;
|
|
comparedWithNull = TryEmit(b.Left, paramExprs, il, ref closure, parent & ~ParentFlags.IgnoreResult);
|
|
}
|
|
else if (b.Left is ConstantExpression l && l.Value == null)
|
|
{
|
|
// the null comparison for nullable is actually a `nullable.HasValue` check,
|
|
// which implies member access on nullable struct - therefore loading it by address
|
|
if (b.Right.Type.IsNullable())
|
|
parent |= ParentFlags.MemberAccess;
|
|
comparedWithNull = TryEmit(b.Right, paramExprs, il, ref closure, parent & ~ParentFlags.IgnoreResult);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!comparedWithNull)
|
|
{
|
|
if (!TryEmit(testExpr, paramExprs, il, ref closure, parent & ~ParentFlags.IgnoreResult))
|
|
return false;
|
|
}
|
|
|
|
var labelIfFalse = il.DefineLabel();
|
|
il.Emit(comparedWithNull && testExpr.NodeType == ExpressionType.Equal ? OpCodes.Brtrue : OpCodes.Brfalse, labelIfFalse);
|
|
|
|
var ifTrueExpr = expr.IfTrue;
|
|
if (!TryEmit(ifTrueExpr, paramExprs, il, ref closure, parent & ParentFlags.IgnoreResult))
|
|
return false;
|
|
|
|
var ifFalseExpr = expr.IfFalse;
|
|
if (ifFalseExpr.NodeType == ExpressionType.Default && ifFalseExpr.Type == typeof(void))
|
|
{
|
|
il.MarkLabel(labelIfFalse);
|
|
return true;
|
|
}
|
|
|
|
var labelDone = il.DefineLabel();
|
|
il.Emit(OpCodes.Br, labelDone);
|
|
|
|
il.MarkLabel(labelIfFalse);
|
|
if (!TryEmit(ifFalseExpr, paramExprs, il, ref closure, parent & ParentFlags.IgnoreResult))
|
|
return false;
|
|
|
|
il.MarkLabel(labelDone);
|
|
return true;
|
|
}
|
|
|
|
private static Expression TryReduceCondition(Expression testExpr)
|
|
{
|
|
if (testExpr is BinaryExpression b)
|
|
{
|
|
if (b.NodeType == ExpressionType.OrElse || b.NodeType == ExpressionType.Or)
|
|
{
|
|
if (b.Left is ConstantExpression l && l.Value is bool lb)
|
|
return lb ? b.Left : TryReduceCondition(b.Right);
|
|
|
|
if (b.Right is ConstantExpression r && r.Value is bool rb && rb == false)
|
|
return TryReduceCondition(b.Left);
|
|
}
|
|
else if (b.NodeType == ExpressionType.AndAlso || b.NodeType == ExpressionType.And)
|
|
{
|
|
if (b.Left is ConstantExpression l && l.Value is bool lb)
|
|
return !lb ? b.Left : TryReduceCondition(b.Right);
|
|
|
|
if (b.Right is ConstantExpression r && r.Value is bool rb && rb)
|
|
return TryReduceCondition(b.Left);
|
|
}
|
|
}
|
|
|
|
return testExpr;
|
|
}
|
|
|
|
private static bool EmitMethodCall(ILGenerator il, MethodInfo method, ParentFlags parent = ParentFlags.Empty)
|
|
{
|
|
if (method == null)
|
|
return false;
|
|
|
|
il.Emit(method.IsVirtual ? OpCodes.Callvirt : OpCodes.Call, method);
|
|
|
|
if ((parent & ParentFlags.IgnoreResult) != 0 && method.ReturnType != typeof(void))
|
|
il.Emit(OpCodes.Pop);
|
|
return true;
|
|
}
|
|
|
|
private static void EmitLoadConstantInt(ILGenerator il, int i)
|
|
{
|
|
switch (i)
|
|
{
|
|
case -1:
|
|
il.Emit(OpCodes.Ldc_I4_M1);
|
|
break;
|
|
case 0:
|
|
il.Emit(OpCodes.Ldc_I4_0);
|
|
break;
|
|
case 1:
|
|
il.Emit(OpCodes.Ldc_I4_1);
|
|
break;
|
|
case 2:
|
|
il.Emit(OpCodes.Ldc_I4_2);
|
|
break;
|
|
case 3:
|
|
il.Emit(OpCodes.Ldc_I4_3);
|
|
break;
|
|
case 4:
|
|
il.Emit(OpCodes.Ldc_I4_4);
|
|
break;
|
|
case 5:
|
|
il.Emit(OpCodes.Ldc_I4_5);
|
|
break;
|
|
case 6:
|
|
il.Emit(OpCodes.Ldc_I4_6);
|
|
break;
|
|
case 7:
|
|
il.Emit(OpCodes.Ldc_I4_7);
|
|
break;
|
|
case 8:
|
|
il.Emit(OpCodes.Ldc_I4_8);
|
|
break;
|
|
default:
|
|
if (i > -129 && i < 128)
|
|
il.Emit(OpCodes.Ldc_I4_S, (sbyte)i);
|
|
else
|
|
il.Emit(OpCodes.Ldc_I4, i);
|
|
break;
|
|
}
|
|
}
|
|
|
|
private static void EmitLoadLocalVariableAddress(ILGenerator il, int location)
|
|
{
|
|
if (location < 256)
|
|
il.Emit(OpCodes.Ldloca_S, (byte)location);
|
|
else
|
|
il.Emit(OpCodes.Ldloca, location);
|
|
}
|
|
|
|
private static void EmitLoadLocalVariable(ILGenerator il, int location)
|
|
{
|
|
if (location == 0)
|
|
il.Emit(OpCodes.Ldloc_0);
|
|
else if (location == 1)
|
|
il.Emit(OpCodes.Ldloc_1);
|
|
else if (location == 2)
|
|
il.Emit(OpCodes.Ldloc_2);
|
|
else if (location == 3)
|
|
il.Emit(OpCodes.Ldloc_3);
|
|
else if (location < 256)
|
|
il.Emit(OpCodes.Ldloc_S, (byte)location);
|
|
else
|
|
il.Emit(OpCodes.Ldloc, location);
|
|
}
|
|
|
|
private static void EmitStoreLocalVariable(ILGenerator il, int location)
|
|
{
|
|
if (location == 0)
|
|
il.Emit(OpCodes.Stloc_0);
|
|
else if (location == 1)
|
|
il.Emit(OpCodes.Stloc_1);
|
|
else if (location == 2)
|
|
il.Emit(OpCodes.Stloc_2);
|
|
else if (location == 3)
|
|
il.Emit(OpCodes.Stloc_3);
|
|
else if (location < 256)
|
|
il.Emit(OpCodes.Stloc_S, (byte)location);
|
|
else
|
|
il.Emit(OpCodes.Stloc, location);
|
|
}
|
|
|
|
private static int EmitStoreLocalVariableAndLoadItsAddress(ILGenerator il, Type type)
|
|
{
|
|
var varIndex = il.GetNextLocalVarIndex(type);
|
|
if (varIndex == 0)
|
|
{
|
|
il.Emit(OpCodes.Stloc_0);
|
|
il.Emit(OpCodes.Ldloca_S, (byte)0);
|
|
}
|
|
else if (varIndex == 1)
|
|
{
|
|
il.Emit(OpCodes.Stloc_1);
|
|
il.Emit(OpCodes.Ldloca_S, (byte)1);
|
|
}
|
|
else if (varIndex == 2)
|
|
{
|
|
il.Emit(OpCodes.Stloc_2);
|
|
il.Emit(OpCodes.Ldloca_S, (byte)2);
|
|
}
|
|
else if (varIndex == 3)
|
|
{
|
|
il.Emit(OpCodes.Stloc_3);
|
|
il.Emit(OpCodes.Ldloca_S, (byte)3);
|
|
}
|
|
else if (varIndex < 256)
|
|
{
|
|
il.Emit(OpCodes.Stloc_S, (byte)varIndex);
|
|
il.Emit(OpCodes.Ldloca_S, (byte)varIndex);
|
|
}
|
|
else
|
|
{
|
|
il.Emit(OpCodes.Stloc, varIndex);
|
|
il.Emit(OpCodes.Ldloca, varIndex);
|
|
}
|
|
|
|
return varIndex;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Helpers targeting the performance. Extensions method names may be a bit funny (non standard),
|
|
// in order to prevent conflicts with YOUR helpers with standard names
|
|
internal static class Tools
|
|
{
|
|
internal static bool IsValueType(this Type type) => type.GetTypeInfo().IsValueType;
|
|
internal static bool IsPrimitive(this Type type) => type.GetTypeInfo().IsPrimitive;
|
|
internal static bool IsClass(this Type type) => type.GetTypeInfo().IsClass;
|
|
|
|
internal static bool IsUnsigned(this Type type) =>
|
|
type == typeof(byte) || type == typeof(ushort) || type == typeof(uint) || type == typeof(ulong);
|
|
|
|
internal static bool IsNullable(this Type type) =>
|
|
type.GetTypeInfo().IsGenericType && type.GetTypeInfo().GetGenericTypeDefinition() == typeof(Nullable<>);
|
|
|
|
internal static MethodInfo FindMethod(this Type type, string methodName)
|
|
{
|
|
var methods = type.GetTypeInfo().DeclaredMethods.AsArray();
|
|
for (var i = 0; i < methods.Length; i++)
|
|
if (methods[i].Name == methodName)
|
|
return methods[i];
|
|
|
|
return type.GetTypeInfo().BaseType?.FindMethod(methodName);
|
|
}
|
|
|
|
internal static MethodInfo DelegateTargetGetterMethod = typeof(Delegate).FindPropertyGetMethod("Target");
|
|
|
|
internal static MethodInfo FindDelegateInvokeMethod(this Type type) => type.FindMethod("Invoke");
|
|
|
|
internal static MethodInfo FindNullableGetValueOrDefaultMethod(this Type type)
|
|
{
|
|
var methods = type.GetTypeInfo().DeclaredMethods.AsArray();
|
|
for (var i = 0; i < methods.Length; i++)
|
|
{
|
|
var m = methods[i];
|
|
if (m.GetParameters().Length == 0 && m.Name == "GetValueOrDefault")
|
|
return m;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
internal static MethodInfo FindValueGetterMethod(this Type type) =>
|
|
type.FindPropertyGetMethod("Value");
|
|
|
|
internal static MethodInfo FindNullableHasValueGetterMethod(this Type type) =>
|
|
type.FindPropertyGetMethod("HasValue");
|
|
|
|
internal static MethodInfo FindPropertyGetMethod(this Type propHolderType, string propName)
|
|
{
|
|
var methods = propHolderType.GetTypeInfo().DeclaredMethods.AsArray();
|
|
for (var i = 0; i < methods.Length; i++)
|
|
{
|
|
var method = methods[i];
|
|
if (method.IsSpecialName)
|
|
{
|
|
var methodName = method.Name;
|
|
if (methodName.Length == propName.Length + 4 && methodName[0] == 'g' && methodName[3] == '_')
|
|
{
|
|
var j = propName.Length - 1;
|
|
while (j != -1 && propName[j] == methodName[j + 4]) --j;
|
|
if (j == -1)
|
|
return method;
|
|
}
|
|
}
|
|
}
|
|
|
|
return propHolderType.GetTypeInfo().BaseType?.FindPropertyGetMethod(propName);
|
|
}
|
|
|
|
internal static MethodInfo FindPropertySetMethod(this Type propHolderType, string propName)
|
|
{
|
|
var methods = propHolderType.GetTypeInfo().DeclaredMethods.AsArray();
|
|
for (var i = 0; i < methods.Length; i++)
|
|
{
|
|
var method = methods[i];
|
|
if (method.IsSpecialName)
|
|
{
|
|
var methodName = method.Name;
|
|
if (methodName.Length == propName.Length + 4 && methodName[0] == 's' && methodName[3] == '_')
|
|
{
|
|
var j = propName.Length - 1;
|
|
while (j != -1 && propName[j] == methodName[j + 4]) --j;
|
|
if (j == -1)
|
|
return method;
|
|
}
|
|
}
|
|
}
|
|
|
|
return propHolderType.GetTypeInfo().BaseType?.FindPropertySetMethod(propName);
|
|
}
|
|
|
|
internal static MethodInfo FindConvertOperator(this Type type, Type sourceType, Type targetType)
|
|
{
|
|
var methods = type.GetTypeInfo().DeclaredMethods.AsArray();
|
|
for (var i = 0; i < methods.Length; i++)
|
|
{
|
|
var m = methods[i];
|
|
if (m.IsStatic && m.IsSpecialName && m.ReturnType == targetType)
|
|
{
|
|
var n = m.Name;
|
|
// n == "op_Implicit" || n == "op_Explicit"
|
|
if (n.Length == 11 &&
|
|
n[2] == '_' && n[5] == 'p' && n[6] == 'l' && n[7] == 'i' && n[8] == 'c' && n[9] == 'i' && n[10] == 't' &&
|
|
m.GetParameters()[0].ParameterType == sourceType)
|
|
return m;
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
internal static ConstructorInfo FindSingleParamConstructor(this Type type, Type paramType)
|
|
{
|
|
var ctors = type.GetTypeInfo().DeclaredConstructors.AsArray();
|
|
for (var i = 0; i < ctors.Length; i++)
|
|
{
|
|
var ctor = ctors[i];
|
|
var parameters = ctor.GetParameters();
|
|
if (parameters.Length == 1 && parameters[0].ParameterType == paramType)
|
|
return ctor;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
public static T[] AsArray<T>(this IEnumerable<T> xs)
|
|
{
|
|
if (xs is T[] array)
|
|
return array;
|
|
return xs == null ? null : xs.ToArray();
|
|
}
|
|
|
|
private static class EmptyArray<T>
|
|
{
|
|
public static readonly T[] Value = new T[0];
|
|
}
|
|
|
|
public static T[] Empty<T>() => EmptyArray<T>.Value;
|
|
|
|
public static T[] WithLast<T>(this T[] source, T value)
|
|
{
|
|
if (source == null || source.Length == 0)
|
|
return new[] { value };
|
|
if (source.Length == 1)
|
|
return new[] { source[0], value };
|
|
if (source.Length == 2)
|
|
return new[] { source[0], source[1], value };
|
|
var sourceLength = source.Length;
|
|
var result = new T[sourceLength + 1];
|
|
Array.Copy(source, 0, result, 0, sourceLength);
|
|
result[sourceLength] = value;
|
|
return result;
|
|
}
|
|
|
|
public static Type[] GetParamTypes(IReadOnlyList<ParameterExpression> paramExprs)
|
|
{
|
|
if (paramExprs == null || paramExprs.Count == 0)
|
|
return Empty<Type>();
|
|
|
|
if (paramExprs.Count == 1)
|
|
return new[] { paramExprs[0].IsByRef ? paramExprs[0].Type.MakeByRefType() : paramExprs[0].Type };
|
|
|
|
var paramTypes = new Type[paramExprs.Count];
|
|
for (var i = 0; i < paramTypes.Length; i++)
|
|
{
|
|
var parameterExpr = paramExprs[i];
|
|
paramTypes[i] = parameterExpr.IsByRef ? parameterExpr.Type.MakeByRefType() : parameterExpr.Type;
|
|
}
|
|
|
|
return paramTypes;
|
|
}
|
|
|
|
public static Type GetFuncOrActionType(Type[] paramTypes, Type returnType)
|
|
{
|
|
if (returnType == typeof(void))
|
|
{
|
|
switch (paramTypes.Length)
|
|
{
|
|
case 0: return typeof(Action);
|
|
case 1: return typeof(Action<>).MakeGenericType(paramTypes);
|
|
case 2: return typeof(Action<,>).MakeGenericType(paramTypes);
|
|
case 3: return typeof(Action<,,>).MakeGenericType(paramTypes);
|
|
case 4: return typeof(Action<,,,>).MakeGenericType(paramTypes);
|
|
case 5: return typeof(Action<,,,,>).MakeGenericType(paramTypes);
|
|
case 6: return typeof(Action<,,,,,>).MakeGenericType(paramTypes);
|
|
case 7: return typeof(Action<,,,,,,>).MakeGenericType(paramTypes);
|
|
default:
|
|
throw new NotSupportedException(
|
|
$"Action with so many ({paramTypes.Length}) parameters is not supported!");
|
|
}
|
|
}
|
|
|
|
switch (paramTypes.Length)
|
|
{
|
|
case 0: return typeof(Func<>).MakeGenericType(returnType);
|
|
case 1: return typeof(Func<,>).MakeGenericType(paramTypes[0], returnType);
|
|
case 2: return typeof(Func<,,>).MakeGenericType(paramTypes[0], paramTypes[1], returnType);
|
|
case 3: return typeof(Func<,,,>).MakeGenericType(paramTypes[0], paramTypes[1], paramTypes[2], returnType);
|
|
case 4: return typeof(Func<,,,,>).MakeGenericType(paramTypes[0], paramTypes[1], paramTypes[2], paramTypes[3], returnType);
|
|
case 5: return typeof(Func<,,,,,>).MakeGenericType(paramTypes[0], paramTypes[1], paramTypes[2], paramTypes[3], paramTypes[4], returnType);
|
|
case 6: return typeof(Func<,,,,,,>).MakeGenericType(paramTypes[0], paramTypes[1], paramTypes[2], paramTypes[3], paramTypes[4], paramTypes[5], returnType);
|
|
case 7: return typeof(Func<,,,,,,,>).MakeGenericType(paramTypes[0], paramTypes[1], paramTypes[2], paramTypes[3], paramTypes[4], paramTypes[5], paramTypes[6], returnType);
|
|
default:
|
|
throw new NotSupportedException(
|
|
$"Func with so many ({paramTypes.Length}) parameters is not supported!");
|
|
}
|
|
}
|
|
|
|
public static T GetFirst<T>(this IEnumerable<T> source)
|
|
{
|
|
// This is pretty much Linq.FirstOrDefault except it does not need to check
|
|
// if source is IPartition<T> (but should it?)
|
|
|
|
if (source is IList<T> list)
|
|
return list.Count == 0 ? default : list[0];
|
|
using (var items = source.GetEnumerator())
|
|
return items.MoveNext() ? items.Current : default;
|
|
}
|
|
|
|
public static T GetFirst<T>(this IList<T> source)
|
|
{
|
|
return source.Count == 0 ? default : source[0];
|
|
}
|
|
|
|
public static T GetFirst<T>(this T[] source)
|
|
{
|
|
return source.Length == 0 ? default : source[0];
|
|
}
|
|
}
|
|
|
|
/// Hey
|
|
public static class ILGeneratorHacks
|
|
{
|
|
/*
|
|
// Original methods from ILGenerator.cs
|
|
|
|
public virtual LocalBuilder DeclareLocal(Type localType)
|
|
{
|
|
return this.DeclareLocal(localType, false);
|
|
}
|
|
|
|
public virtual LocalBuilder DeclareLocal(Type localType, bool pinned)
|
|
{
|
|
MethodBuilder methodBuilder = this.m_methodBuilder as MethodBuilder;
|
|
if ((MethodInfo)methodBuilder == (MethodInfo)null)
|
|
throw new NotSupportedException();
|
|
if (methodBuilder.IsTypeCreated())
|
|
throw new InvalidOperationException(SR.InvalidOperation_TypeHasBeenCreated);
|
|
if (localType == (Type)null)
|
|
throw new ArgumentNullException(nameof(localType));
|
|
if (methodBuilder.m_bIsBaked)
|
|
throw new InvalidOperationException(SR.InvalidOperation_MethodBaked);
|
|
this.m_localSignature.AddArgument(localType, pinned);
|
|
LocalBuilder localBuilder = new LocalBuilder(this.m_localCount, localType, (MethodInfo)methodBuilder, pinned);
|
|
++this.m_localCount;
|
|
return localBuilder;
|
|
}
|
|
*/
|
|
|
|
/// Not allocating the LocalBuilder class
|
|
/// emitting this:
|
|
/// il.m_localSignature.AddArgument(type);
|
|
/// return PostInc(ref il.LocalCount);
|
|
public static Func<ILGenerator, Type, int> CompileGetNextLocalVarIndex()
|
|
{
|
|
if (LocalCountField == null || LocalSignatureField == null || AddArgumentMethod == null)
|
|
return (i, t) => i.DeclareLocal(t).LocalIndex;
|
|
|
|
var method = new DynamicMethod(string.Empty,
|
|
typeof(int), new[] { typeof(ExpressionCompiler.ArrayClosure), typeof(ILGenerator), typeof(Type) },
|
|
typeof(ExpressionCompiler.ArrayClosure), skipVisibility: true);
|
|
|
|
var il = method.GetILGenerator();
|
|
|
|
il.Emit(OpCodes.Ldarg_1); // load `il` argument
|
|
il.Emit(OpCodes.Ldfld, LocalSignatureField);
|
|
il.Emit(OpCodes.Ldarg_2); // load `type` argument
|
|
il.Emit(OpCodes.Ldc_I4_0); // load `pinned: false` argument
|
|
il.Emit(OpCodes.Call, AddArgumentMethod);
|
|
|
|
il.Emit(OpCodes.Ldarg_1); // load `il` argument
|
|
il.Emit(OpCodes.Ldflda, LocalCountField);
|
|
il.Emit(OpCodes.Call, PostIncMethod);
|
|
|
|
il.Emit(OpCodes.Ret);
|
|
|
|
return (Func<ILGenerator, Type, int>)method.CreateDelegate(typeof(Func<ILGenerator, Type, int>), ExpressionCompiler.EmptyArrayClosure);
|
|
}
|
|
|
|
internal static int PostInc(ref int i) => i++;
|
|
|
|
public static readonly MethodInfo PostIncMethod = typeof(ILGeneratorHacks).GetTypeInfo()
|
|
.GetDeclaredMethod(nameof(PostInc));
|
|
|
|
/// Get via reflection
|
|
public static readonly FieldInfo LocalSignatureField = typeof(ILGenerator).GetTypeInfo()
|
|
.GetDeclaredField("m_localSignature");
|
|
|
|
/// Get via reflection
|
|
public static readonly FieldInfo LocalCountField = typeof(ILGenerator).GetTypeInfo()
|
|
.GetDeclaredField("m_localCount");
|
|
|
|
/// Get via reflection
|
|
public static readonly MethodInfo AddArgumentMethod = typeof(SignatureHelper).GetTypeInfo()
|
|
.GetDeclaredMethods(nameof(SignatureHelper.AddArgument)).First(m => m.GetParameters().Length == 2);
|
|
|
|
private static readonly Func<ILGenerator, Type, int> _getNextLocalVarIndex = CompileGetNextLocalVarIndex();
|
|
|
|
/// Does the job
|
|
public static int GetNextLocalVarIndex(this ILGenerator il, Type t) => _getNextLocalVarIndex(il, t);
|
|
}
|
|
|
|
internal struct LiveCountArray<T>
|
|
{
|
|
public int Count;
|
|
public T[] Items;
|
|
|
|
public LiveCountArray(T[] items)
|
|
{
|
|
Items = items;
|
|
Count = items.Length;
|
|
}
|
|
|
|
public ref T PushSlot()
|
|
{
|
|
if (++Count > Items.Length)
|
|
Items = Expand(Items);
|
|
return ref Items[Count - 1];
|
|
}
|
|
|
|
public void PushSlot(T item)
|
|
{
|
|
if (++Count > Items.Length)
|
|
Items = Expand(Items);
|
|
Items[Count - 1] = item;
|
|
}
|
|
|
|
public void Pop() => --Count;
|
|
|
|
private static T[] Expand(T[] items)
|
|
{
|
|
if (items.Length == 0)
|
|
return new T[4];
|
|
|
|
var count = items.Length;
|
|
var newItems = new T[count << 1]; // count x 2
|
|
Array.Copy(items, 0, newItems, 0, count);
|
|
return newItems;
|
|
}
|
|
}
|
|
}
|
|
#endif
|