diff --git a/src/FastExpressionCompiler.LightExpression/Expression.cs b/src/FastExpressionCompiler.LightExpression/Expression.cs index 6be7058d..a9e08bad 100644 --- a/src/FastExpressionCompiler.LightExpression/Expression.cs +++ b/src/FastExpressionCompiler.LightExpression/Expression.cs @@ -74,7 +74,7 @@ public abstract class Expression /// and available in the `closure` structure. Find the expression examples below by searching `IsIntrinsic => true`. [RequiresUnreferencedCode(Trimming.Message)] - public virtual bool TryEmit(ref CompilerContext context, ILGenerator il, ParentFlags parent, int byRefIndex = -1) => false; + public virtual bool TryEmit(ref CompilerContext context, ILGenerator il, int byRefIndex = -1) => false; public virtual bool IsCustomToCSharpString => false; @@ -3337,9 +3337,9 @@ public override Result TryCollectInfo(ref CompilerContext context, NestedLambdaI ExpressionCompiler.TryCollectInfo(ref context, Operand, nestedLambda, ref rootNestedLambdas); [RequiresUnreferencedCode(Trimming.Message)] - public override bool TryEmit(ref CompilerContext context, ILGenerator il, ParentFlags parent, int byRefIndex = -1) + public override bool TryEmit(ref CompilerContext context, ILGenerator il, int byRefIndex = -1) { - if (!EmittingVisitor.TryEmit(Operand, il, ref context, parent, byRefIndex)) + if (!EmittingVisitor.TryEmit(Operand, il, ref context, context.CurrentParentFlags, byRefIndex)) return false; il.Demit(OpCodes.Ldftn, Operand.Type.FindDelegateInvokeMethod()); il.Demit(OpCodes.Newobj, Type.GetConstructors()[0]); @@ -3379,9 +3379,9 @@ public override Result TryCollectInfo(ref CompilerContext context, NestedLambdaI ExpressionCompiler.TryCollectInfo(ref context, Operand, nestedLambda, ref rootNestedLambdas); [RequiresUnreferencedCode(Trimming.Message)] - public override bool TryEmit(ref CompilerContext context, ILGenerator il, ParentFlags parent, int byRefIndex = -1) + public override bool TryEmit(ref CompilerContext context, ILGenerator il, int byRefIndex = -1) { - if (!EmittingVisitor.TryEmit(Operand, il, ref context, parent, byRefIndex)) + if (!EmittingVisitor.TryEmit(Operand, il, ref context, context.CurrentParentFlags, byRefIndex)) return false; if (Type == typeof(object)) { @@ -3930,7 +3930,7 @@ internal NoArgsNewClassIntrinsicExpression(ConstructorInfo constructor) : base(c public override Result TryCollectInfo(ref CompilerContext context, NestedLambdaInfo nestedLambda, ref SmallList rootNestedLambdas) => 0; [RequiresUnreferencedCode(Trimming.Message)] - public override bool TryEmit(ref CompilerContext context, ILGenerator il, ParentFlags parent, int byRefIndex = -1) + public override bool TryEmit(ref CompilerContext context, ILGenerator il, int byRefIndex = -1) { il.Demit(OpCodes.Newobj, Constructor); return true; @@ -3957,9 +3957,9 @@ public override Result TryCollectInfo(ref CompilerContext context, NestedLambdaI ExpressionCompiler.TryCollectInfo(ref context, Argument, nestedLambda, ref rootNestedLambdas); [RequiresUnreferencedCode(Trimming.Message)] - public override bool TryEmit(ref CompilerContext context, ILGenerator il, ParentFlags parent, int byRefIndex = -1) + public override bool TryEmit(ref CompilerContext context, ILGenerator il, int byRefIndex = -1) { - var ok = EmittingVisitor.TryEmit(Argument, il, ref context, parent | ParentFlags.CtorCall, -1); + var ok = EmittingVisitor.TryEmit(Argument, il, ref context, context.CurrentParentFlags | ParentFlags.CtorCall, -1); il.Demit(OpCodes.Newobj, Constructor); return ok; } @@ -3993,9 +3993,9 @@ public override Result TryCollectInfo(ref CompilerContext context, NestedLambdaI } [RequiresUnreferencedCode(Trimming.Message)] - public override bool TryEmit(ref CompilerContext context, ILGenerator il, ParentFlags parent, int byRefIndex = -1) + public override bool TryEmit(ref CompilerContext context, ILGenerator il, int byRefIndex = -1) { - var f = parent | ParentFlags.CtorCall; + var f = context.CurrentParentFlags | ParentFlags.CtorCall; var ok = EmittingVisitor.TryEmit(Argument0, il, ref context, f, -1) && EmittingVisitor.TryEmit(Argument1, il, ref context, f, -1); @@ -4036,9 +4036,9 @@ public override Result TryCollectInfo(ref CompilerContext context, NestedLambdaI } [RequiresUnreferencedCode(Trimming.Message)] - public override bool TryEmit(ref CompilerContext context, ILGenerator il, ParentFlags parent, int byRefIndex = -1) + public override bool TryEmit(ref CompilerContext context, ILGenerator il, int byRefIndex = -1) { - var f = parent | ParentFlags.CtorCall; + var f = context.CurrentParentFlags | ParentFlags.CtorCall; var ok = EmittingVisitor.TryEmit(Argument0, il, ref context, f, -1) && EmittingVisitor.TryEmit(Argument1, il, ref context, f, -1) && @@ -4082,9 +4082,9 @@ public override Result TryCollectInfo(ref CompilerContext context, NestedLambdaI } [RequiresUnreferencedCode(Trimming.Message)] - public override bool TryEmit(ref CompilerContext context, ILGenerator il, ParentFlags parent, int byRefIndex = -1) + public override bool TryEmit(ref CompilerContext context, ILGenerator il, int byRefIndex = -1) { - var f = parent | ParentFlags.CtorCall; + var f = context.CurrentParentFlags | ParentFlags.CtorCall; var ok = EmittingVisitor.TryEmit(Argument0, il, ref context, f, -1) && EmittingVisitor.TryEmit(Argument1, il, ref context, f, -1) && @@ -4132,9 +4132,9 @@ public override Result TryCollectInfo(ref CompilerContext context, NestedLambdaI } [RequiresUnreferencedCode(Trimming.Message)] - public override bool TryEmit(ref CompilerContext context, ILGenerator il, ParentFlags parent, int byRefIndex = -1) + public override bool TryEmit(ref CompilerContext context, ILGenerator il, int byRefIndex = -1) { - var f = parent | ParentFlags.CtorCall; + var f = context.CurrentParentFlags | ParentFlags.CtorCall; var ok = EmittingVisitor.TryEmit(Argument0, il, ref context, f, -1) && EmittingVisitor.TryEmit(Argument1, il, ref context, f, -1) && @@ -4186,9 +4186,9 @@ public override Result TryCollectInfo(ref CompilerContext context, NestedLambdaI } [RequiresUnreferencedCode(Trimming.Message)] - public override bool TryEmit(ref CompilerContext context, ILGenerator il, ParentFlags parent, int byRefIndex = -1) + public override bool TryEmit(ref CompilerContext context, ILGenerator il, int byRefIndex = -1) { - var f = parent | ParentFlags.CtorCall; + var f = context.CurrentParentFlags | ParentFlags.CtorCall; var ok = EmittingVisitor.TryEmit(Argument0, il, ref context, f, -1) && EmittingVisitor.TryEmit(Argument1, il, ref context, f, -1) && @@ -4243,9 +4243,9 @@ public override Result TryCollectInfo(ref CompilerContext context, NestedLambdaI } [RequiresUnreferencedCode(Trimming.Message)] - public override bool TryEmit(ref CompilerContext context, ILGenerator il, ParentFlags parent, int byRefIndex = -1) + public override bool TryEmit(ref CompilerContext context, ILGenerator il, int byRefIndex = -1) { - var f = parent | ParentFlags.CtorCall; + var f = context.CurrentParentFlags | ParentFlags.CtorCall; var ok = EmittingVisitor.TryEmit(Argument0, il, ref context, f, -1) && EmittingVisitor.TryEmit(Argument1, il, ref context, f, -1) && @@ -4285,9 +4285,9 @@ public override Result TryCollectInfo(ref CompilerContext context, NestedLambdaI } [RequiresUnreferencedCode(Trimming.Message)] - public override bool TryEmit(ref CompilerContext context, ILGenerator il, ParentFlags parent, int byRefIndex = -1) + public override bool TryEmit(ref CompilerContext context, ILGenerator il, int byRefIndex = -1) { - var f = parent | ParentFlags.CtorCall; + var f = context.CurrentParentFlags | ParentFlags.CtorCall; var args = Args; for (var i = 0; i < args.Count; i++) if (!EmittingVisitor.TryEmit(args[i], il, ref context, f, -1)) diff --git a/src/FastExpressionCompiler.LightExpression/Properties/AssemblyInfo.cs b/src/FastExpressionCompiler.LightExpression/Properties/AssemblyInfo.cs new file mode 100644 index 00000000..97675007 --- /dev/null +++ b/src/FastExpressionCompiler.LightExpression/Properties/AssemblyInfo.cs @@ -0,0 +1,4 @@ +using System.Runtime.CompilerServices; + +// Reuse the repository signing key so the signed LightExpression issue-tests assembly can override internal LightExpression members. +[assembly: InternalsVisibleTo("FastExpressionCompiler.LightExpression.IssueTests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100C3EE5DD15505AED491F6EFFE157E3EC3694E4EC3A532D3C16E497AB1B0C3CA9FB2959D870E24831B600B576E66B82DDA14F0FD88860D8EA05547454B7FC77201D2082FB320D5E609BBAF853A16D5AC459A9585AF6B48C796B22EBB70472C5412C997F68D6E5A044DE3B0DE7B95D1569EE57BF72469F23C748F5879E50A8D50B2")] diff --git a/src/FastExpressionCompiler/FastExpressionCompiler.cs b/src/FastExpressionCompiler/FastExpressionCompiler.cs index 7780f141..812a1d84 100644 --- a/src/FastExpressionCompiler/FastExpressionCompiler.cs +++ b/src/FastExpressionCompiler/FastExpressionCompiler.cs @@ -611,13 +611,19 @@ public struct LabelInfo } /// Track the info required to build a closure object + some context information not directly related to closure. - public struct CompilerContext - { - /// Tracks that the last emit was an address - public bool LastEmitIsAddress; - - // Tracks the current block nesting count in the stack of blocks in Collect and Emit phase - private ushort _blockCount; + public struct CompilerContext + { + /// Tracks that the last emit was an address + public bool LastEmitIsAddress; + + /// Tracks the expressions currently being emitted; the most recently entered expression is at the end. + internal SmallList, NoArrayPool> EmitExpressions; + + /// Tracks the parent flags for the corresponding entries in . + internal SmallList, NoArrayPool> EmitParentFlags; + + // Tracks the current block nesting count in the stack of blocks in Collect and Emit phase + private ushort _blockCount; /// Tracks the use of the variables in the blocks stack per variable, /// (uint) contains (ushort) BlockIndex in the upper bits and (ushort) VarIndex in the lower bits. @@ -666,20 +672,63 @@ public struct CompilerContext public CompilerFlags CompilerFlags; /// Populates the info - public CompilerContext(ClosureStatus status, ParamExprs paramExprs, CompilerFlags compilerFlags) - { - Status = status; - Constants = new SmallList(); - LastEmitIsAddress = false; + public CompilerContext(ClosureStatus status, ParamExprs paramExprs, CompilerFlags compilerFlags) + { + Status = status; + Constants = new SmallList(); + LastEmitIsAddress = false; CurrentInlinedLambdaInvokeIndex = -1; - ParamExprs = paramExprs; - CompilerFlags = compilerFlags; - } - - /// Populates info directly with provided closure object and constants. - public CompilerContext(ClosureStatus status, object[] constValues, ParamExprs paramExprs, CompilerFlags compilerFlags) - { - Status = status; + ParamExprs = paramExprs; + CompilerFlags = compilerFlags; + } + + /// The number of expressions in the current emit stack. + public int EmitExpressionCount + { + [MethodImpl((MethodImplOptions)256)] + get => EmitExpressions.Count; + } + + /// The currently emitted expression or null when the emit stack is empty. + public Expression CurrentEmitExpression + { + [MethodImpl((MethodImplOptions)256)] + get => EmitExpressions.Count != 0 ? EmitExpressions.GetLastSurePresentItem() : null; + } + + /// The parent flags for the currently emitted expression. + public ParentFlags CurrentParentFlags + { + [MethodImpl((MethodImplOptions)256)] + get => EmitParentFlags.Count != 0 ? EmitParentFlags.GetLastSurePresentItem() : ParentFlags.Empty; + } + + /// Gets the current expression or its parent by zero-based distance from the current expression. + [MethodImpl((MethodImplOptions)256)] + public Expression GetEmitExpression(int indexFromCurrent = 0) + { + var exprIndex = EmitExpressions.Count - 1 - indexFromCurrent; + return exprIndex >= 0 ? EmitExpressions.GetSurePresentRef(exprIndex) : null; + } + + [MethodImpl((MethodImplOptions)256)] + internal void PushEmitContext(Expression expr, ParentFlags parent) + { + EmitExpressions.Add(expr); + EmitParentFlags.Add(parent); + } + + [MethodImpl((MethodImplOptions)256)] + internal void PopEmitContext() + { + EmitExpressions.RemoveLastSurePresentItem(); + EmitParentFlags.RemoveLastSurePresentItem(); + } + + /// Populates info directly with provided closure object and constants. + public CompilerContext(ClosureStatus status, object[] constValues, ParamExprs paramExprs, CompilerFlags compilerFlags) + { + Status = status; Constants = new SmallList(constValues ?? Tools.Empty()); if (constValues != null) @@ -688,9 +737,9 @@ public CompilerContext(ClosureStatus status, object[] constValues, ParamExprs pa LastEmitIsAddress = false; CurrentInlinedLambdaInvokeIndex = -1; - ParamExprs = paramExprs; - CompilerFlags = compilerFlags; - } + ParamExprs = paramExprs; + CompilerFlags = compilerFlags; + } [MethodImpl((MethodImplOptions)256)] public bool ContainsConstantsOrNestedLambdas() => Constants.Count != 0 | NestedLambdas.Count != 0; @@ -1890,22 +1939,25 @@ public static class EmittingVisitor private static readonly MethodInfo _objectEqualsMethod = ((Func)object.Equals).Method; - public static bool TryEmit(Expression expr, ILGenerator il, ref CompilerContext context, ParentFlags parent, int byRefIndex = -1) - { - var exprType = expr.Type; - while (true) - { - context.LastEmitIsAddress = false; -#if LIGHT_EXPRESSION - if (expr.IsIntrinsic) - return expr.TryEmit(ref context, il, parent, byRefIndex); -#endif - var nodeType = expr.NodeType; - switch (nodeType) - { - case ExpressionType.Parameter: - return (parent & ParentFlags.IgnoreResult) != 0 || - TryEmitParameter((ParameterExpression)expr, il, ref context, parent, byRefIndex); + public static bool TryEmit(Expression expr, ILGenerator il, ref CompilerContext context, ParentFlags parent, int byRefIndex = -1) + { + context.PushEmitContext(expr, parent); + try + { + var exprType = expr.Type; + while (true) + { + context.LastEmitIsAddress = false; +#if LIGHT_EXPRESSION + if (expr.IsIntrinsic) + return expr.TryEmit(ref context, il, byRefIndex); +#endif + var nodeType = expr.NodeType; + switch (nodeType) + { + case ExpressionType.Parameter: + return (parent & ParentFlags.IgnoreResult) != 0 || + TryEmitParameter((ParameterExpression)expr, il, ref context, parent, byRefIndex); case ExpressionType.TypeAs: case ExpressionType.IsTrue: @@ -2236,13 +2288,17 @@ public static bool TryEmit(Expression expr, ILGenerator il, ref CompilerContext case ExpressionType.DebugInfo: // todo: @feature - is not supported yet return true; // todo: @unclear - just ignoring the info for now - case ExpressionType.Quote: // todo: @feature - is not supported yet - default: - return false; - - } - } - } + case ExpressionType.Quote: // todo: @feature - is not supported yet + default: + return false; + } + } + } + finally + { + context.PopEmitContext(); + } + } private static bool TryEmitNew(Expression expr, ILGenerator il, ref CompilerContext context, ParentFlags parent) { diff --git a/test/FastExpressionCompiler.LightExpression.IssueTests/FastExpressionCompiler.LightExpression.IssueTests.csproj b/test/FastExpressionCompiler.LightExpression.IssueTests/FastExpressionCompiler.LightExpression.IssueTests.csproj index d10a3535..c89b9d39 100644 --- a/test/FastExpressionCompiler.LightExpression.IssueTests/FastExpressionCompiler.LightExpression.IssueTests.csproj +++ b/test/FastExpressionCompiler.LightExpression.IssueTests/FastExpressionCompiler.LightExpression.IssueTests.csproj @@ -1,10 +1,12 @@  - - net472;net6.0;net8.0;net9.0 - net472;net9.0 - - LIGHT_EXPRESSION - + + net472;net6.0;net8.0;net9.0 + net472;net9.0 + + LIGHT_EXPRESSION + true + ..\..\FastExpressionCompiler.snk + diff --git a/test/FastExpressionCompiler.LightExpression.IssueTests/Issue500_TryEmit_context_exposes_full_parent_expression_stack.cs b/test/FastExpressionCompiler.LightExpression.IssueTests/Issue500_TryEmit_context_exposes_full_parent_expression_stack.cs new file mode 100644 index 00000000..1e7dcff8 --- /dev/null +++ b/test/FastExpressionCompiler.LightExpression.IssueTests/Issue500_TryEmit_context_exposes_full_parent_expression_stack.cs @@ -0,0 +1,65 @@ +using System; +using System.Linq.Expressions; +using System.Reflection.Emit; +using FastExpressionCompiler.LightExpression.ImTools; + +using static FastExpressionCompiler.LightExpression.Expression; +using SysExpr = System.Linq.Expressions.Expression; + +namespace FastExpressionCompiler.LightExpression.IssueTests +{ + public class Issue500_TryEmit_context_exposes_full_parent_expression_stack : ITestX + { + public void Run(TestRun t) + { + Compiler_context_contains_the_full_emit_expression_stack_for_intrinsic_expression(); + } + + public void Compiler_context_contains_the_full_emit_expression_stack_for_intrinsic_expression() + { + var marker = new InspectingIntrinsicExpression(41); + var expr = Lambda>(Add(Constant(1), Convert(marker, typeof(int)))); + + var compiled = expr.CompileFast(flags: CompilerFlags.ThrowOnNotSupportedExpression); + + Asserts.AreEqual(42, compiled()); + Asserts.AreEqual(1, marker.EmitCount); + } + + private sealed class InspectingIntrinsicExpression : Expression + { + private readonly int _value; + + public int EmitCount; + + public InspectingIntrinsicExpression(int value) => _value = value; + + public override ExpressionType NodeType => ExpressionType.Extension; + public override Type Type => typeof(int); + public override bool IsIntrinsic => true; + + public override ExpressionCompiler.Result TryCollectInfo(ref ExpressionCompiler.CompilerContext context, + ExpressionCompiler.NestedLambdaInfo nestedLambda, + ref SmallList rootNestedLambdas) => 0; + + public override bool TryEmit(ref ExpressionCompiler.CompilerContext context, ILGenerator il, int byRefIndex = -1) + { + ++EmitCount; + + Asserts.AreSame(this, context.CurrentEmitExpression); + Asserts.AreEqual(3, context.EmitExpressionCount); + Asserts.AreEqual(ExpressionType.Convert, context.GetEmitExpression(1).NodeType); + Asserts.AreEqual(ExpressionType.Add, context.GetEmitExpression(2).NodeType); + Asserts.IsNull(context.GetEmitExpression(3)); + + il.Emit(OpCodes.Ldc_I4, _value); + return true; + } + + protected internal override Expression Accept(ExpressionVisitor visitor) => this; + + internal override SysExpr CreateSysExpression(ref SmallList exprsConverted) => + SysExpr.Constant(_value, Type); + } + } +} diff --git a/test/FastExpressionCompiler.TestsRunner/Program.cs b/test/FastExpressionCompiler.TestsRunner/Program.cs index 8a9ac85e..21f4fb44 100644 --- a/test/FastExpressionCompiler.TestsRunner/Program.cs +++ b/test/FastExpressionCompiler.TestsRunner/Program.cs @@ -74,6 +74,7 @@ public static void Main() lt.Run(new LightExpression.IssueTests.Issue473_InvalidProgramException_when_using_Expression_Condition_with_converted_decimal_expression()); lt.Run(new LightExpression.IssueTests.Issue476_System_ExecutionEngineException_with_nullables_on_repeated_calls_to_ConcurrentDictionary()); lt.Run(new LightExpression.IssueTests.Issue499_InvalidProgramException_for_Sorting_and_comparison_function()); + lt.Run(new LightExpression.IssueTests.Issue500_TryEmit_context_exposes_full_parent_expression_stack()); RunAllTests(); }