dadhi/FastExpressionCompiler

The JIT compiler encountered invalid IL code or an internal limitation

cda963 opened this issue · 14 comments

I'm using FastExpressionCompiler v2.0.0 along with Mapster (latest version) in a gRPC .Net Core project where I need to convert from a POCO object to a proto object, and everything works just fine.

When I try upgrading FastExpressionCompiler to any version above 2.0.0, the conversion throws an exception with the message "The JIT compiler encountered invalid IL code or an internal limitation".

Example: TenantConfig is my source object (a simple .Net object with a few string/boolean properties), and Protos.TenantConfig is the .proto equivalent of my .Net object:

var failure = new TenantConfig().Adapt<Protos.TenantConfig>();

@cda963 Please provide the complete code for TenantConfig and Protos.TenantConfig. Every property matters, actually.

This is my .Net object

public class TenantConfig
{
	public string Name { get; set; }

	public string Value { get; set; }

	public bool IsEncrypted { get; set; }
}

The Protos.TenantConfig is an auto-generated file based on the the following .proto file:

syntax = "proto3";

option csharp_namespace = "Kq.Protos";

package tenants;

service Tenants
{
	rpc TenantConfig (TenantConfigRequest) returns (TenantConfigResponse);
}

message TenantConfigRequest
{
	string tenantId = 1;
}

message TenantConfigResponse
{
	repeated TenantConfig TenantConfig = 1;
}

message TenantConfig
{
	string id = 1;
	string name = 2;
	string value = 3;
}

Below is the auto-generated file:

using System;
using System.CodeDom.Compiler;
using System.Diagnostics;
using Google.Protobuf;
using Google.Protobuf.Reflection;

namespace Kq.Protos;

public sealed class TenantConfig : IMessage<TenantConfig>, IMessage, IEquatable<TenantConfig>, IDeepCloneable<TenantConfig>, IBufferMessage
{
    private static readonly MessageParser<TenantConfig> _parser = new MessageParser<TenantConfig>(() => new TenantConfig());

    private UnknownFieldSet _unknownFields;

    public const int IdFieldNumber = 1;

    private string id_ = "";

    public const int NameFieldNumber = 2;

    private string name_ = "";

    public const int ValueFieldNumber = 3;

    private string value_ = "";

    [DebuggerNonUserCode]
    [GeneratedCode("protoc", null)]
    public static MessageParser<TenantConfig> Parser => _parser;

    [DebuggerNonUserCode]
    [GeneratedCode("protoc", null)]
    public static MessageDescriptor Descriptor => TenantsReflection.Descriptor.MessageTypes[13];

    [DebuggerNonUserCode]
    [GeneratedCode("protoc", null)]
    MessageDescriptor IMessage.Descriptor => Descriptor;

    [DebuggerNonUserCode]
    [GeneratedCode("protoc", null)]
    public string Id
    {
        get
        {
            return id_;
        }
        set
        {
            id_ = ProtoPreconditions.CheckNotNull(value, "value");
        }
    }

    [DebuggerNonUserCode]
    [GeneratedCode("protoc", null)]
    public string Name
    {
        get
        {
            return name_;
        }
        set
        {
            name_ = ProtoPreconditions.CheckNotNull(value, "value");
        }
    }

    [DebuggerNonUserCode]
    [GeneratedCode("protoc", null)]
    public string Value
    {
        get
        {
            return value_;
        }
        set
        {
            value_ = ProtoPreconditions.CheckNotNull(value, "value");
        }
    }

    [DebuggerNonUserCode]
    [GeneratedCode("protoc", null)]
    public TenantConfig()
    {
    }

    [DebuggerNonUserCode]
    [GeneratedCode("protoc", null)]
    public TenantConfig(TenantConfig other)
        : this()
    {
        id_ = other.id_;
        name_ = other.name_;
        value_ = other.value_;
        _unknownFields = UnknownFieldSet.Clone(other._unknownFields);
    }

    [DebuggerNonUserCode]
    [GeneratedCode("protoc", null)]
    public TenantConfig Clone()
    {
        return new TenantConfig(this);
    }

    [DebuggerNonUserCode]
    [GeneratedCode("protoc", null)]
    public override bool Equals(object other)
    {
        return Equals(other as TenantConfig);
    }

    [DebuggerNonUserCode]
    [GeneratedCode("protoc", null)]
    public bool Equals(TenantConfig other)
    {
        if (other == null)
        {
            return false;
        }

        if (other == this)
        {
            return true;
        }

        if (Id != other.Id)
        {
            return false;
        }

        if (Name != other.Name)
        {
            return false;
        }

        if (Value != other.Value)
        {
            return false;
        }

        return object.Equals(_unknownFields, other._unknownFields);
    }

    [DebuggerNonUserCode]
    [GeneratedCode("protoc", null)]
    public override int GetHashCode()
    {
        int num = 1;
        if (Id.Length != 0)
        {
            num ^= Id.GetHashCode();
        }

        if (Name.Length != 0)
        {
            num ^= Name.GetHashCode();
        }

        if (Value.Length != 0)
        {
            num ^= Value.GetHashCode();
        }

        if (_unknownFields != null)
        {
            num ^= _unknownFields.GetHashCode();
        }

        return num;
    }

    [DebuggerNonUserCode]
    [GeneratedCode("protoc", null)]
    public override string ToString()
    {
        return JsonFormatter.ToDiagnosticString(this);
    }

    [DebuggerNonUserCode]
    [GeneratedCode("protoc", null)]
    public void WriteTo(CodedOutputStream output)
    {
        output.WriteRawMessage(this);
    }

    [DebuggerNonUserCode]
    [GeneratedCode("protoc", null)]
    void IBufferMessage.InternalWriteTo(ref WriteContext output)
    {
        if (Id.Length != 0)
        {
            output.WriteRawTag(10);
            output.WriteString(Id);
        }

        if (Name.Length != 0)
        {
            output.WriteRawTag(18);
            output.WriteString(Name);
        }

        if (Value.Length != 0)
        {
            output.WriteRawTag(26);
            output.WriteString(Value);
        }

        if (_unknownFields != null)
        {
            _unknownFields.WriteTo(ref output);
        }
    }

    [DebuggerNonUserCode]
    [GeneratedCode("protoc", null)]
    public int CalculateSize()
    {
        int num = 0;
        if (Id.Length != 0)
        {
            num += 1 + CodedOutputStream.ComputeStringSize(Id);
        }

        if (Name.Length != 0)
        {
            num += 1 + CodedOutputStream.ComputeStringSize(Name);
        }

        if (Value.Length != 0)
        {
            num += 1 + CodedOutputStream.ComputeStringSize(Value);
        }

        if (_unknownFields != null)
        {
            num += _unknownFields.CalculateSize();
        }

        return num;
    }

    [DebuggerNonUserCode]
    [GeneratedCode("protoc", null)]
    public void MergeFrom(TenantConfig other)
    {
        if (other != null)
        {
            if (other.Id.Length != 0)
            {
                Id = other.Id;
            }

            if (other.Name.Length != 0)
            {
                Name = other.Name;
            }

            if (other.Value.Length != 0)
            {
                Value = other.Value;
            }

            _unknownFields = UnknownFieldSet.MergeFrom(_unknownFields, other._unknownFields);
        }
    }

    [DebuggerNonUserCode]
    [GeneratedCode("protoc", null)]
    public void MergeFrom(CodedInputStream input)
    {
        input.ReadRawMessage(this);
    }

    [DebuggerNonUserCode]
    [GeneratedCode("protoc", null)]
    void IBufferMessage.InternalMergeFrom(ref ParseContext input)
    {
        uint num;
        while ((num = input.ReadTag()) != 0)
        {
            switch (num)
            {
                default:
                    _unknownFields = UnknownFieldSet.MergeFieldFrom(_unknownFields, ref input);
                    break;
                case 10u:
                    Id = input.ReadString();
                    break;
                case 18u:
                    Name = input.ReadString();
                    break;
                case 26u:
                    Value = input.ReadString();
                    break;
            }
        }
    }
}
#if false // Decompilation log
'430' items in cache
------------------
Resolve: 'System.Runtime, Version=8.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a'
Found single assembly: 'System.Runtime, Version=8.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a'
Load from: 'C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\8.0.3\ref\net8.0\System.Runtime.dll'
------------------
Resolve: 'Google.Protobuf, Version=3.23.1.0, Culture=neutral, PublicKeyToken=a7d26565bac4d604'
Found single assembly: 'Google.Protobuf, Version=3.25.0.0, Culture=neutral, PublicKeyToken=a7d26565bac4d604'
WARN: Version mismatch. Expected: '3.23.1.0', Got: '3.25.0.0'
Load from: 'C:\Users\Dragos\.nuget\packages\google.protobuf\3.25.0\lib\net5.0\Google.Protobuf.dll'
------------------
Resolve: 'Grpc.Core.Api, Version=2.0.0.0, Culture=neutral, PublicKeyToken=d754f35622e28bad'
Found single assembly: 'Grpc.Core.Api, Version=2.0.0.0, Culture=neutral, PublicKeyToken=d754f35622e28bad'
Load from: 'C:\Users\Dragos\.nuget\packages\grpc.core.api\2.62.0\lib\netstandard2.1\Grpc.Core.Api.dll'
------------------
Resolve: 'System.Collections, Version=8.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a'
Found single assembly: 'System.Collections, Version=8.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a'
Load from: 'C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\8.0.3\ref\net8.0\System.Collections.dll'
------------------
Resolve: 'System.Memory, Version=8.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51'
Found single assembly: 'System.Memory, Version=8.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51'
Load from: 'C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\8.0.3\ref\net8.0\System.Memory.dll'
------------------
Resolve: 'System.Runtime.InteropServices, Version=3.1.0.0, Culture=neutral, PublicKeyToken=null'
Found single assembly: 'System.Runtime.InteropServices, Version=8.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a'
WARN: Version mismatch. Expected: '3.1.0.0', Got: '8.0.0.0'
Load from: 'C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\8.0.3\ref\net8.0\System.Runtime.InteropServices.dll'
------------------
Resolve: 'System.Runtime.CompilerServices.Unsafe, Version=3.1.0.0, Culture=neutral, PublicKeyToken=null'
Found single assembly: 'System.Runtime.CompilerServices.Unsafe, Version=8.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a'
WARN: Version mismatch. Expected: '3.1.0.0', Got: '8.0.0.0'
Load from: 'C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\8.0.3\ref\net8.0\System.Runtime.CompilerServices.Unsafe.dll'
#endif

Just to double-check. You still have the error with FEC v4.2.0?

Yes, I just tried upgrading a couple of hours ago.

Ok. I will check. The thing is simple so not sure what is going on here.

What are the TargetFrameworks?

Thanks.

<TargetFramework>net8.0</TargetFramework>

Ok, I do see the Id field in the Proto and no IsEncrypted.
How do they map?
What is Mapster Config for the mapping?

Indeed I have changed last night the POCO properties and haven't updated the proto file.. this is what 14hrs work day does to you :). I'll test again and come back if this is still an issue. I apologize for this oversight.


Converting a single object works with v4.2.0, but converting a list of objects doesn't.

	var single = new TenantConfig
	{
		Name = Guid.NewGuid().ToString(),
		Value = Guid.NewGuid().ToString(),
		IsEncrypted = true
	}.Adapt<Protos.TenantConfig>();

	var list = new List<TenantConfig>()
	{
		new() 
		{
			Name = Guid.NewGuid().ToString(),
			Value = Guid.NewGuid().ToString(),
			IsEncrypted = true
		}
	};

	var exceptionHere = list.Adapt<List<Protos.TenantConfig>>();

I can work around this, by creating a list of converted objects.

Ok, sorry to hear about your 14h wday. Will check the new example.

@cda963 I have added the list adapter to test and it is working fine. Check here 0f908c4

Maybe I am missing some details?

In your test I see var failure = new TenantConfig().Adapt<TenantConfigVal>(); which tests for a simple object conversion, not a list. I couldn't find the part where you test against a list of objects.

Yep, it is here

public void Issue410_The_JIT_compiler_encountered_invalid_IL_code_or_an_internal_limitation()

Yes, I see it now.
The issue I'm facing is in a large solution with many projects.
Let me try this in an empty project, and I'll get back to you.
Thanks.

dadhi commented

Closing, as it was not updated in quite some time