dadhi/FastExpressionCompiler

System.InvalidProgramException : The JIT compiler encountered invalid IL code or an internal limitation.

michaelgrosner opened this issue · 3 comments

Hello,

I've been trying out FastExpressionCompiler in order to replace my usages of Expression.Compile. I am running into a situation that I can't figure out why FEC breaks while Expression.Compile handles the expression.

In pseudo-code, I am trying to get a RefFunc<T, bool> which is "Obj.X > 2 * Obj?.Nested?.Y". "X" and "Y" are both nullable doubles, and "Obj" and "Nested" are classes with other fields.

This is what ToExpressionString returns for me:

var p = new ParameterExpression[1]; // the parameter expressions
var e = new Expression[18]; // the unique expressions
var expr = MakeBinary(ExpressionType.GreaterThan,
  e[0]=Condition(
    e[1]=MakeBinary(ExpressionType.Equal,
      p[0]=Parameter(typeof(Test.Obj).MakeByRefType(), "Obj"),
      e[2]=Constant(null)),
    e[3]=Constant(null, typeof(double?)),
    e[4]=Property(
      p[0 // (Test.Obj Obj)
        ],
      typeof(Test.Obj).GetTypeInfo().GetDeclaredProperty("X")),
    typeof(double?)),
  e[5]=MakeBinary(ExpressionType.Multiply,
    e[6]=Convert(
      e[7]=Constant(2),
      typeof(double?)),
    e[8]=Condition(
      e[9]=MakeBinary(ExpressionType.Equal,
        e[10]=Condition(
          e[11]=MakeBinary(ExpressionType.Equal,
            p[0 // (Test.Obj Obj)
              ],
            e[12]=Constant(null)),
          e[13]=Constant(null, typeof(Test.Nested)),
          e[14]=Property(
            p[0 // (Test.Obj Obj)
              ],
            typeof(Test.Obj).GetTypeInfo().GetDeclaredProperty("Nested")),
          typeof(Test.Nested)),
        e[15]=Constant(null)),
      e[16]=Constant(null, typeof(double?)),
      e[17]=Property(
        e[10 // Conditional of Test.Nested
          ],
        typeof(Test.Nested).GetTypeInfo().GetDeclaredProperty("Y")),
      typeof(double?)),
    liftToNull: true,
    null));

I think it's probably due to the nested null checks in "Obj?.Nested?.Y". But the standard Expression.Compile handles this just fine. Do you have any insight into how to fix this, either in this library or in my code? I have seen this issue when running on 4.2.1 and 4.0.0 on .NET 6.

@michaelgrosner Could you provide the missing Obj and Nested types so that I can compile and test this expression?
You may look into the last tests in the IssueTests project to see what I need.

Sure, here's something that reproduces the issue:

using System.Linq.Expressions;
using System.Reflection;
using FastExpressionCompiler;
using static System.Linq.Expressions.Expression;

public class Obj(double? x, Nested? nested)
{
    public double? X { get; } = x;
    public Nested? Nested { get; } = nested;
}

public class Nested(double? y)
{
    public double? Y { get; } = y;
}

public delegate TOut RefFunc<TIn, TOut>(in TIn t);

public class Program
{
    public static void Main()
    {
        var p = new ParameterExpression[1]; // the parameter expressions
        var e = new Expression[19]; // the unique expressions
        var expr = Lambda<RefFunc<Obj, bool>>(
          e[0] = MakeBinary(ExpressionType.GreaterThan,
            e[1] = Condition(
              e[2] = MakeBinary(ExpressionType.Equal,
                p[0] = Parameter(typeof(Obj).MakeByRefType(), "p"),
                e[3] = Constant(null)),
              e[4] = Constant(null, typeof(double?)),
              e[5] = Property(
                p[0 // (Obj p)
                  ],
                typeof(Obj).GetTypeInfo().GetDeclaredProperty("X")),
              typeof(double?)),
            e[6] = MakeBinary(ExpressionType.Multiply,
              e[7] = Convert(
                e[8] = Constant(2),
                typeof(double?)),
              e[9] = Condition(
                e[10] = MakeBinary(ExpressionType.Equal,
                  e[11] = Condition(
                    e[12] = MakeBinary(ExpressionType.Equal,
                      p[0 // (Obj p)
                        ],
                      e[13] = Constant(null)),
                    e[14] = Constant(null, typeof(Nested)),
                    e[15] = Property(
                      p[0 // (Obj p)
                        ],
                      typeof(Obj).GetTypeInfo().GetDeclaredProperty("Nested")),
                    typeof(Nested)),
                  e[16] = Constant(null)),
                e[17] = Constant(null, typeof(double?)),
                e[18] = Property(
                  e[11 // Conditional of Nested
                    ],
                  typeof(Nested).GetTypeInfo().GetDeclaredProperty("Y")),
                typeof(double?)),
              liftToNull: true,
              null)),
          p[0 // (Obj p)
            ]);
        var compiled = expr.CompileFast();

        // Unhandled exception. System.InvalidProgramException: The JIT compiler encountered invalid IL code or an internal limitation.
        compiled(new Obj(5, new Nested(6)));
    }
}

I was also able to reproduce it this way, too:

    public class Obj(double? a, Nested nested)
    {
        public double? A { get; } = a;
        public Nested? Nested { get; } = nested;
    }

    public class Nested(double? b)
    {
        public double? B { get; } = b;
    }

    [Test]
    public void Issue419()
    {
        Expression<Func<Obj, bool>> expression = d => 
            (d == null ? null : d.A) > (double?)3 * ((d == null ? null : d.Nested) == null ? null : d.Nested.B);
        Console.WriteLine(expression.Body.ToExpressionString());
        var regular = expression.Compile();
        var fast = expression.CompileFast();
        var data = new Obj(10, new Nested(5));
        Console.WriteLine(regular(data));
        Console.WriteLine(fast(data)); // also throws System.InvalidProgramException
    }

@michaelgrosner Thank you for the refined test, adding to the queue to fix.