Skip to content

Commit 601f682

Browse files
committed
support inc/dec with method
1 parent 734314e commit 601f682

4 files changed

Lines changed: 267 additions & 31 deletions

File tree

‎src/FastExpressionCompiler.LightExpression/Expression.cs‎

Lines changed: 36 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -655,13 +655,16 @@ private static UnaryExpression GetMethodBasedUnary(ExpressionType nodeType, Expr
655655

656656
var operandType = operand.Type;
657657
var type = method.ReturnType;
658+
659+
// the pre/post increment/decrement assign nodes (contiguous in the enum) have the type of the assigned operand
660+
var isIncDecAssign = nodeType >= ExpressionType.PreIncrementAssign && nodeType <= ExpressionType.PostDecrementAssign;
658661
if (ps[0].IsAssignableFrom(operandType))
659-
return new MethodUnaryExpression(nodeType, operand, type, method);
662+
return new MethodUnaryExpression(nodeType, operand, isIncDecAssign ? operandType : type, method);
660663

661664
// check for lifted call
662665
if (operandType.IsNullable() && ps[0].IsAssignableFrom(operandType.GetUnderlyingNullableTypeUnsafe()) &&
663666
type.IsValueType && !type.IsNullable())
664-
return new MethodUnaryExpression(nodeType, operand, type.GetNullable(), method);
667+
return new MethodUnaryExpression(nodeType, operand, isIncDecAssign ? operandType : type.GetNullable(), method);
665668

666669
throw new InvalidOperationException($"OperandTypesDoNotMatchParameters for {nodeType} and method {method.Name}");
667670
}
@@ -711,6 +714,26 @@ public static UnaryExpression OnesComplement(Expression expression, MethodInfo m
711714
public static UnaryExpression UnaryPlus(Expression expression, MethodInfo method) =>
712715
method == null ? UnaryPlus(expression) : GetMethodBasedUnary(ExpressionType.UnaryPlus, expression, method);
713716

717+
/// <summary>Creates a UnaryExpression that represents the assignment of the expression incremented by 1 and returns the incremented value, with the implementing method.</summary>
718+
[RequiresUnreferencedCode(Trimming.Message)]
719+
public static UnaryExpression PreIncrementAssign(Expression expression, MethodInfo method) =>
720+
method == null ? PreIncrementAssign(expression) : GetMethodBasedUnary(ExpressionType.PreIncrementAssign, expression, method);
721+
722+
/// <summary>Creates a UnaryExpression that represents the assignment of the expression incremented by 1 and returns the original value, with the implementing method.</summary>
723+
[RequiresUnreferencedCode(Trimming.Message)]
724+
public static UnaryExpression PostIncrementAssign(Expression expression, MethodInfo method) =>
725+
method == null ? PostIncrementAssign(expression) : GetMethodBasedUnary(ExpressionType.PostIncrementAssign, expression, method);
726+
727+
/// <summary>Creates a UnaryExpression that represents the assignment of the expression decremented by 1 and returns the decremented value, with the implementing method.</summary>
728+
[RequiresUnreferencedCode(Trimming.Message)]
729+
public static UnaryExpression PreDecrementAssign(Expression expression, MethodInfo method) =>
730+
method == null ? PreDecrementAssign(expression) : GetMethodBasedUnary(ExpressionType.PreDecrementAssign, expression, method);
731+
732+
/// <summary>Creates a UnaryExpression that represents the assignment of the expression decremented by 1 and returns the original value, with the implementing method.</summary>
733+
[RequiresUnreferencedCode(Trimming.Message)]
734+
public static UnaryExpression PostDecrementAssign(Expression expression, MethodInfo method) =>
735+
method == null ? PostDecrementAssign(expression) : GetMethodBasedUnary(ExpressionType.PostDecrementAssign, expression, method);
736+
714737
/// <summary>Creates a UnaryExpression, given an operand, its type and a method</summary>
715738
[RequiresUnreferencedCode(Trimming.Message)]
716739
public static UnaryExpression MakeUnary(ExpressionType unaryType, Expression operand, Type type, MethodInfo method = null) =>
@@ -732,10 +755,10 @@ public static UnaryExpression MakeUnary(ExpressionType unaryType, Expression ope
732755
ExpressionType.Unbox => Unbox(operand, type),
733756
ExpressionType.Increment => Increment(operand, method),
734757
ExpressionType.Decrement => Decrement(operand, method),
735-
ExpressionType.PreIncrementAssign => method == null ? PreIncrementAssign(operand) : ThrowNotSupportedUnary("PreIncrementAssign with method is not supported"),
736-
ExpressionType.PostIncrementAssign => method == null ? PostIncrementAssign(operand) : ThrowNotSupportedUnary("PostIncrementAssign with method is not supported"),
737-
ExpressionType.PreDecrementAssign => method == null ? PreDecrementAssign(operand) : ThrowNotSupportedUnary("PreDecrementAssign with method is not supported"),
738-
ExpressionType.PostDecrementAssign => method == null ? PostDecrementAssign(operand) : ThrowNotSupportedUnary("PostDecrementAssign with method is not supported"),
758+
ExpressionType.PreIncrementAssign => PreIncrementAssign(operand, method),
759+
ExpressionType.PostIncrementAssign => PostIncrementAssign(operand, method),
760+
ExpressionType.PreDecrementAssign => PreDecrementAssign(operand, method),
761+
ExpressionType.PostDecrementAssign => PostDecrementAssign(operand, method),
739762
_ => ThrowNotSupportedUnary($"Making UnaryExpression of {unaryType} is not supported")
740763
};
741764

@@ -3423,10 +3446,10 @@ internal override SysExpr CreateSysExpression(ref SmallList<LightAndSysExpr> exp
34233446
case ExpressionType.Negate: return SysExpr.Negate(Operand.ToExpression(ref exprsConverted), Method);
34243447
case ExpressionType.NegateChecked: return SysExpr.NegateChecked(Operand.ToExpression(ref exprsConverted), Method);
34253448
case ExpressionType.OnesComplement: return SysExpr.OnesComplement(Operand.ToExpression(ref exprsConverted), Method);
3426-
case ExpressionType.PostDecrementAssign: return SysExpr.PostDecrementAssign(Operand.ToExpression(ref exprsConverted));
3427-
case ExpressionType.PostIncrementAssign: return SysExpr.PostIncrementAssign(Operand.ToExpression(ref exprsConverted));
3428-
case ExpressionType.PreDecrementAssign: return SysExpr.PreDecrementAssign(Operand.ToExpression(ref exprsConverted));
3429-
case ExpressionType.PreIncrementAssign: return SysExpr.PreIncrementAssign(Operand.ToExpression(ref exprsConverted));
3449+
case ExpressionType.PostDecrementAssign: return SysExpr.PostDecrementAssign(Operand.ToExpression(ref exprsConverted), Method);
3450+
case ExpressionType.PostIncrementAssign: return SysExpr.PostIncrementAssign(Operand.ToExpression(ref exprsConverted), Method);
3451+
case ExpressionType.PreDecrementAssign: return SysExpr.PreDecrementAssign(Operand.ToExpression(ref exprsConverted), Method);
3452+
case ExpressionType.PreIncrementAssign: return SysExpr.PreIncrementAssign(Operand.ToExpression(ref exprsConverted), Method);
34303453
case ExpressionType.Quote: return SysExpr.Quote(Operand.ToExpression(ref exprsConverted));
34313454
case ExpressionType.UnaryPlus: return SysExpr.UnaryPlus(Operand.ToExpression(ref exprsConverted), Method);
34323455
case ExpressionType.Unbox: return SysExpr.Unbox(Operand.ToExpression(ref exprsConverted), Type);
@@ -6044,14 +6067,16 @@ internal TypedReturnManyParametersExpression(Expression body, IReadOnlyList<Para
60446067
public sealed class DynamicExpression : Expression
60456068
{
60466069
public override ExpressionType NodeType => ExpressionType.Dynamic;
6047-
public override Type Type => typeof(object);
6070+
public override Type Type { get;}
60486071
public Type DelegateType { get; }
60496072
public CallSiteBinder Binder { get; }
60506073
public IReadOnlyList<Expression> Arguments { get; }
60516074

6075+
[RequiresUnreferencedCode(Trimming.Message)]
60526076
public DynamicExpression(Type delegateType, CallSiteBinder binder, IReadOnlyList<Expression> arguments)
60536077
{
60546078
DelegateType = delegateType;
6079+
Type = delegateType.FindDelegateInvokeMethod().ReturnType;
60556080
Binder = binder;
60566081
Arguments = arguments;
60576082
}

‎src/FastExpressionCompiler/FastExpressionCompiler.cs‎

Lines changed: 107 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2199,13 +2199,27 @@ public static bool TryEmit(Expression expr, ILGenerator il, ref CompilerContext
21992199

22002200
case ExpressionType.PostIncrementAssign:
22012201
case ExpressionType.PreIncrementAssign:
2202-
return TryEmitArithmeticAndOrAssign(((UnaryExpression)expr).Operand, null, exprType, ExpressionType.Add,
2203-
nodeType == ExpressionType.PostIncrementAssign, il, ref context, parent);
2202+
{
2203+
var incExpr = (UnaryExpression)expr;
2204+
var incMethod = incExpr.Method;
2205+
return incMethod != null
2206+
? TryEmitOpAssignWithMethod(incExpr.Operand, null, incMethod, ExpressionType.Add,
2207+
nodeType == ExpressionType.PostIncrementAssign, il, ref context, parent)
2208+
: TryEmitArithmeticAndOrAssign(incExpr.Operand, null, exprType, ExpressionType.Add,
2209+
nodeType == ExpressionType.PostIncrementAssign, il, ref context, parent);
2210+
}
22042211

22052212
case ExpressionType.PostDecrementAssign:
22062213
case ExpressionType.PreDecrementAssign:
2207-
return TryEmitArithmeticAndOrAssign(((UnaryExpression)expr).Operand, null, exprType, ExpressionType.Subtract,
2208-
nodeType == ExpressionType.PostDecrementAssign, il, ref context, parent);
2214+
{
2215+
var decExpr = (UnaryExpression)expr;
2216+
var decMethod = decExpr.Method;
2217+
return decMethod != null
2218+
? TryEmitOpAssignWithMethod(decExpr.Operand, null, decMethod, ExpressionType.Subtract,
2219+
nodeType == ExpressionType.PostDecrementAssign, il, ref context, parent)
2220+
: TryEmitArithmeticAndOrAssign(decExpr.Operand, null, exprType, ExpressionType.Subtract,
2221+
nodeType == ExpressionType.PostDecrementAssign, il, ref context, parent);
2222+
}
22092223

22102224
case ExpressionType.AddAssign:
22112225
case ExpressionType.AddAssignChecked:
@@ -2223,8 +2237,17 @@ public static bool TryEmit(Expression expr, ILGenerator il, ref CompilerContext
22232237
case ExpressionType.RightShiftAssign:
22242238
case ExpressionType.Assign:
22252239
var ba = (BinaryExpression)expr;
2226-
return TryEmitArithmeticAndOrAssign(ba.Left, ba.Right, exprType,
2227-
AssignToArithmeticOrSelf(nodeType), false, il, ref context, parent);
2240+
if (nodeType == ExpressionType.Assign)
2241+
return TryEmitArithmeticAndOrAssign(ba.Left, ba.Right, exprType, nodeType, false, il, ref context, parent);
2242+
2243+
if (ba.Conversion != null) // todo: @feature the conversion lambda of the compound assignment is not supported yet, better to fail than to produce the wrong result
2244+
return false;
2245+
2246+
var baMethod = ba.Method;
2247+
return baMethod != null
2248+
? TryEmitOpAssignWithMethod(ba.Left, ba.Right, baMethod, AssignToArithmeticOrSelf(nodeType), false, il, ref context, parent)
2249+
: TryEmitArithmeticAndOrAssign(ba.Left, ba.Right, exprType,
2250+
AssignToArithmeticOrSelf(nodeType), false, il, ref context, parent);
22282251

22292252
case ExpressionType.Block:
22302253
{
@@ -4623,6 +4646,84 @@ private static bool TryEmitArithmeticAndOrAssign(
46234646
return true;
46244647
}
46254648

4649+
// Lowers `left op= right` (or `left++` / `left--` when the right is null) with the explicit or user-defined method into
4650+
// `left = method(left, right)` the same way as System reduces it, with the temps for the target instance and indexes to evaluate them once.
4651+
private static bool TryEmitOpAssignWithMethod(Expression left, Expression right, MethodInfo method, ExpressionType arithType,
4652+
bool isPost, ILGenerator il, ref CompilerContext context, ParentFlags parent)
4653+
{
4654+
SmallList<ParameterExpression, Stack8<ParameterExpression>, NoArrayPool<ParameterExpression>> vars = default;
4655+
SmallList<Expression, Stack8<Expression>, NoArrayPool<Expression>> stmts = default;
4656+
var target = left;
4657+
if (left is MemberExpression memberExpr)
4658+
{
4659+
var obj = memberExpr.Expression;
4660+
if (obj != null && !(obj is ParameterExpression || obj is ConstantExpression))
4661+
{
4662+
if (obj.Type.IsValueType) // the temp copy of the struct would lose the mutation
4663+
return false;
4664+
var objVar = Expression.Variable(obj.Type);
4665+
vars.Add(objVar);
4666+
stmts.Add(Expression.Assign(objVar, obj));
4667+
target = Expression.MakeMemberAccess(objVar, memberExpr.Member);
4668+
}
4669+
}
4670+
else if (left is IndexExpression indexExpr)
4671+
{
4672+
var obj = indexExpr.Object;
4673+
var indexArgs = indexExpr.GetArgExprs();
4674+
var indexArgCount = indexArgs.GetArgCount();
4675+
var args = new Expression[indexArgCount];
4676+
if (obj != null && !(obj is ParameterExpression | obj is ConstantExpression))
4677+
{
4678+
if (obj.Type.IsValueType)
4679+
return false;
4680+
var objVar = Expression.Variable(obj.Type);
4681+
vars.Add(objVar);
4682+
stmts.Add(Expression.Assign(objVar, obj));
4683+
obj = objVar;
4684+
}
4685+
for (var i = 0; i < indexArgCount; ++i)
4686+
{
4687+
var arg = indexArgs.GetArgument(i);
4688+
if (!(arg is ParameterExpression | arg is ConstantExpression))
4689+
{
4690+
var argVar = Expression.Variable(arg.Type);
4691+
vars.Add(argVar);
4692+
stmts.Add(Expression.Assign(argVar, arg));
4693+
arg = argVar;
4694+
}
4695+
args[i] = arg;
4696+
}
4697+
target = indexExpr.Indexer != null
4698+
? Expression.MakeIndex(obj, indexExpr.Indexer, args)
4699+
: Expression.ArrayAccess(obj, args);
4700+
}
4701+
else if (!(left is ParameterExpression))
4702+
return false;
4703+
4704+
// the old value is needed only for the post increment/decrement with the used result
4705+
var oldVar = isPost && !parent.IgnoresResult() ? Expression.Variable(left.Type) : null;
4706+
var operand = target;
4707+
if (oldVar != null)
4708+
{
4709+
vars.Add(oldVar);
4710+
stmts.Add(Expression.Assign(oldVar, target));
4711+
operand = oldVar;
4712+
}
4713+
4714+
Expression value = right != null
4715+
? Expression.MakeBinary(arithType, operand, right, false, method)
4716+
: arithType == ExpressionType.Add
4717+
? Expression.Increment(operand, method)
4718+
: Expression.Decrement(operand, method);
4719+
stmts.Add(Expression.Assign(target, value));
4720+
if (oldVar != null)
4721+
stmts.Add(oldVar);
4722+
4723+
var lowered = vars.Count == 0 ? stmts[0] : Expression.Block(vars.CopyToNewArray(), stmts.CopyToNewArray());
4724+
return TryEmit(lowered, il, ref context, parent);
4725+
}
4726+
46264727
private static ExpressionType AssignToArithmeticOrSelf(ExpressionType nodeType) => nodeType switch
46274728
{
46284729
ExpressionType.AddAssign => ExpressionType.Add,

‎src/FastExpressionCompiler/ImTools.cs‎

Lines changed: 26 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -850,8 +850,11 @@ public interface ISmallList<T> : IIndexed<T>, IEnumerable<T>
850850
/// <summary>Clears the list.</summary>
851851
void Clear();
852852

853-
/// <summary>Copies the contents of the list to the passed array.</summary>
854-
void CopyToArray(T[] arr, int copyToIndex = 0);
853+
/// <summary>Copies the contents of the list to the passed array. Copies into the new array starting from the specified index and for the specified count</summary>
854+
void CopyToArray(T[] arr, int copyToIndex = 0, int copyCount = -1);
855+
856+
/// <summary>Copies the contents of the list to a new array. Copies into the new array starting from the specified index and for the specified count.</summary>
857+
T[] CopyToNewArray(int copyToIndex = 0, int copyCount = -1);
855858
}
856859

857860
/// <summary>Generic version of SmallList abstracted for how much items are on the stack</summary>
@@ -1048,23 +1051,33 @@ public void TryRemoveLastNoDrop()
10481051
}
10491052

10501053
/// <inheritdoc/>
1051-
public void CopyToArray(T[] arr, int copyToIndex = 0)
1054+
public void CopyToArray(T[] arr, int copyToIndex = 0, int copyCount = -1)
10521055
{
1053-
if (_count == 0) return;
1056+
if (copyCount == -1) copyCount = _count;
1057+
if (copyCount == 0) return;
10541058

10551059
Debug.Assert(arr != null, "Target array should not be null");
1056-
Debug.Assert(arr.Length - copyToIndex >= _count,
1057-
$"Target array is too small {arr.Length} to hold {_count} items starting from index {copyToIndex}");
10581060

1059-
for (var i = 0; i < arr.Length; ++i)
1061+
var i = 0;
1062+
var arrLength = arr.Length;
1063+
var arrCap = arrLength - copyToIndex;
1064+
var stackCopyCount = Math.Min(Stack.Capacity, Math.Min(copyCount, arrCap));
1065+
for (; i < stackCopyCount; ++i)
10601066
arr[copyToIndex + i] = Stack.GetSurePresentRef(i);
10611067

1062-
var restCount = _count - Stack.Capacity;
1063-
if (restCount > 0)
1064-
{
1065-
Debug.Assert(Rest != null && Rest.Length >= restCount, $"Expecting the Rest of {restCount} but found only {Rest?.Length.ToString() ?? "`null`"}");
1066-
Array.Copy(Rest, 0, arr, copyToIndex + Stack.Capacity, restCount);
1067-
}
1068+
copyCount -= stackCopyCount;
1069+
arrCap -= stackCopyCount;
1070+
var restCopyCount = Math.Min(Rest?.Length ?? 0, Math.Min(copyCount - Stack.Capacity, arrCap - Stack.Capacity));
1071+
if (restCopyCount > 0)
1072+
Array.Copy(Rest, 0, arr, copyToIndex + Stack.Capacity, restCopyCount);
1073+
}
1074+
1075+
/// <summary>Copies the contents of the list to a new array. Copies into the new array starting from the specified index and for the specified count.</summary>
1076+
public T[] CopyToNewArray(int copyToIndex = 0, int copyCount = -1)
1077+
{
1078+
var arr = new T[copyCount == -1 ? _count : copyCount];
1079+
CopyToArray(arr, copyToIndex, copyCount);
1080+
return arr;
10681081
}
10691082

10701083
/// <summary>Returns an enumerator struct</summary>

0 commit comments

Comments
 (0)