Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Crash on using Expression.Compile() in a release mode (iOS). #69410

Closed
JaneySprings opened this issue May 16, 2022 · 33 comments
Closed

Crash on using Expression.Compile() in a release mode (iOS). #69410

JaneySprings opened this issue May 16, 2022 · 33 comments
Assignees
Milestone

Comments

@JaneySprings
Copy link

JaneySprings commented May 16, 2022

Description

Please see following project to reproduce this issue: https://github.com/JaneySprings/MauiReflectionBug
Same code works perfectly in Xamarin.Forms (iOS release).

Steps to Reproduce

  1. Open project folder in terminal
  2. Run 'dotnet build -t:run -f:net6.0-ios -p:_DeviceName=insert_device_id_here /p:RuntimeIdentifier=ios-arm64 -c release'

Version with bug

Release Candidate 3 (current)

Last version that worked well

Unknown/Other

Affected platforms

iOS

Affected platform versions

iOS 15.4

Did you find any workaround?

no

Relevant log output

 System.ExecutionEngineException: Attempting to JIT compile method (wrapper delegate-invoke) int <Module>:invoke_callvirt_int_MyItem (reflecBug.MyItem) while running in aot-only mode. See https://docs.microsoft.com/xamarin/ios/internals/limitations for more information.
2022-05-16 15:16:40.965 reflecBug[93626:11651593]    at System.Linq.Expressions.Interpreter.FuncCallInstruction 2[[reflecBug.MyItem, reflecBug, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null],[System.Int32, System.Private.CoreLib, Version=6.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]].Run(InterpretedFrame )
   at System.Linq.Expressions.Interpreter.Interpreter.Run(InterpretedFrame )
   at System.Linq.Expressions.Interpreter.LightLambda.Run(Object[] )
2022-05-16 15:16:40.965 reflecBug[93626:11651593]    at System.Dynamic.Utils.DelegateHelpers.FuncThunk1[Object,Object](Func`2 handler, Object t1)
   at reflecBug.MainPage.GetValue(Object component)
   at reflecBug.MainPage..ctor()
@Eilon
Copy link
Member

Eilon commented May 16, 2022

@rolfbjarne
Copy link
Member

@Eilon this should go to dotnet/runtime, it's an issue with either the AOT compiler or the BCL.

@ghost ghost added the untriaged New issue has not been triaged by the area owner label May 16, 2022
@ghost
Copy link

ghost commented May 16, 2022

Tagging subscribers to this area: @cston
See info in area-owners.md if you want to be subscribed.

Issue Details

Description

Please see following project to reproduce this issue: https://github.com/JaneySprings/MauiReflectionBug
Same code works perfectly in Xamarin.Forms (iOS release).

Steps to Reproduce

  1. Open project folder in terminal
  2. Run 'dotnet build -t:run -f:net6.0-ios -p:_DeviceName=insert_device_id_here /p:RuntimeIdentifier=ios-arm64 -c release'

Version with bug

Release Candidate 3 (current)

Last version that worked well

Unknown/Other

Affected platforms

iOS

Affected platform versions

iOS 15.4

Did you find any workaround?

no

Relevant log output

System.ExecutionEngineException: Attempting to JIT compile method (wrapper delegate-invoke) int :invoke_callvirt_int_MyItem (reflecBug.MyItem) while running in aot-only mode. See https://docs.microsoft.com/xamarin/ios/internals/limitations for more information.
2022-05-16 15:16:40.965 reflecBug[93626:11651593] at System.Linq.Expressions.Interpreter.FuncCallInstruction 2[[reflecBug.MyItem, reflecBug, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null],[System.Int32, System.Private.CoreLib, Version=6.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]].Run(InterpretedFrame )
at System.Linq.Expressions.Interpreter.Interpreter.Run(InterpretedFrame )
at System.Linq.Expressions.Interpreter.LightLambda.Run(Object[] )
2022-05-16 15:16:40.965 reflecBug[93626:11651593] at System.Dynamic.Utils.DelegateHelpers.FuncThunk1[Object,Object](Func`2 handler, Object t1)
at reflecBug.MainPage.GetValue(Object component)
at reflecBug.MainPage..ctor()

Author: JaneySprings
Assignees: -
Labels:

area-System.Linq.Expressions, untriaged, t/bug, platform/iOS 🍎, fatal

Milestone: -

@Eilon Eilon transferred this issue from dotnet/maui May 16, 2022
@huoyaoyuan huoyaoyuan added os-ios Apple iOS and removed platform/iOS 🍎 labels May 17, 2022
@ghost
Copy link

ghost commented May 17, 2022

Tagging subscribers to 'os-ios': @steveisok, @akoeplinger
See info in area-owners.md if you want to be subscribed.

Issue Details

Description

Please see following project to reproduce this issue: https://github.com/JaneySprings/MauiReflectionBug
Same code works perfectly in Xamarin.Forms (iOS release).

Steps to Reproduce

  1. Open project folder in terminal
  2. Run 'dotnet build -t:run -f:net6.0-ios -p:_DeviceName=insert_device_id_here /p:RuntimeIdentifier=ios-arm64 -c release'

Version with bug

Release Candidate 3 (current)

Last version that worked well

Unknown/Other

Affected platforms

iOS

Affected platform versions

iOS 15.4

Did you find any workaround?

no

Relevant log output

System.ExecutionEngineException: Attempting to JIT compile method (wrapper delegate-invoke) int :invoke_callvirt_int_MyItem (reflecBug.MyItem) while running in aot-only mode. See https://docs.microsoft.com/xamarin/ios/internals/limitations for more information.
2022-05-16 15:16:40.965 reflecBug[93626:11651593] at System.Linq.Expressions.Interpreter.FuncCallInstruction 2[[reflecBug.MyItem, reflecBug, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null],[System.Int32, System.Private.CoreLib, Version=6.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]].Run(InterpretedFrame )
at System.Linq.Expressions.Interpreter.Interpreter.Run(InterpretedFrame )
at System.Linq.Expressions.Interpreter.LightLambda.Run(Object[] )
2022-05-16 15:16:40.965 reflecBug[93626:11651593] at System.Dynamic.Utils.DelegateHelpers.FuncThunk1[Object,Object](Func`2 handler, Object t1)
at reflecBug.MainPage.GetValue(Object component)
at reflecBug.MainPage..ctor()

Author: JaneySprings
Assignees: -
Labels:

area-System.Linq.Expressions, untriaged, os-ios, t/bug, fatal

Milestone: -

@steveisok steveisok removed the untriaged New issue has not been triaged by the area owner label May 17, 2022
@steveisok
Copy link
Member

I can reproduce in .net 6. Under .NET 7, the message is a bit more friendly

Expression must be readable (Parameter 'expression') (System.ArgumentException)
   at System.Dynamic.Utils.ExpressionUtils.RequiresCanRead(Expression , String , Int32 )
   at System.Linq.Expressions.Expression.Convert(Expression , Type , MethodInfo )
   at System.Linq.Expressions.Expression.Convert(Expression , Type )
   at Program.CompileLambda()
   at Program.GetValue(Object )
   at Program.Main(String[] )
   at Program.<Main>(String[] )

@akoeplinger do you know of any mono trickery that was done in the past?

@MichalStrehovsky do you have any insight into if this can even work in runtime?

@MichalStrehovsky
Copy link
Member

MichalStrehovsky commented May 18, 2022

@MichalStrehovsky do you have any insight into if this can even work in runtime?

Following adapted snippet placed in a console app works with NativeAOT full AOT mode, so this looks like it has more to do with how S.L.Expressions is configured for iDevices than with AOT:

using System.Linq.Expressions;

var item = new MyItem();
item.Id = 100;

try
{
    Console.WriteLine(GetValue(item));
}
catch (Exception e)
{
    Console.WriteLine(e);
}

static object GetValue(object component)
{
    Func<object, object> propertyAccessorDelegate = CompileLambda();
    return propertyAccessorDelegate(component);
}

static Func<object, object> CompileLambda()
{
    ParameterExpression parameter = Expression.Parameter(typeof(object), "p");
    MemberExpression property = Expression.Property(Expression.Convert(parameter, typeof(MyItem)), "Id");
    Expression<Func<object, object>> lambda = Expression.Lambda<Func<object, object>>(Expression.Convert(property, typeof(object)), parameter);
    return lambda.Compile();
}

public class BaseItem
{
    int id;

    public virtual int Id
    {
        get => id;
        set => this.id = value;
    }
}

public class MyItem : BaseItem
{
    int id;

    //Some attributes....
    public override int Id { get => base.Id; set => base.Id = value; }
}

The Expression must be readable (Parameter 'expression') (System.ArgumentException) failure mode that @steveisok hit looks suspicious - it would indicate an issue with the LINQ expression interpreter and I would expect it to be hittable with CoreCLR by swapping return lambda.Compile() in the repro case with return lambda.Compile(preferInterpretation: true). This forces LINQ expressions to go down the interpreter paths even if Ref.Emit is available. But it's not hittable on CoreCLR (without full AOT) with the above repro.

The originally reported issue ("Attempting to JIT compile method") might be related to what I called out in #61952 in my comments around iDevices/iOS: there are more codepaths in S.L.Expressions that should be activated for a full AOT experience. Some of these codepaths require extra runtime support - it's code around CanEmitObjectArrayDelegate and CanCreateArbitraryDelegates.

I think the tests that got disabled for iDevices in #54970 are testing this functionality but it doesn't look like the failures got looked at since then.

@AntonKosenkoDX
Copy link

Hello, we are developing DevExpress .NET MAUI controls and experienced the same issue a lot in our DevExpress.Data library that provides many helpful classes to our customers and for internal use. We use Expression.Compile() very widely in our codebase and we didn't experience any issues with it in Xamarin.Forms, but experienced random crashes a lot in MAUI. There is another one same issue, that we've reported recently - #71323. That issues have a very high priority for us because we can't release our components with random crashes. Could you provide any generic workaround for these issues? Thank you for your help.

@vargaz
Copy link
Contributor

vargaz commented Jun 7, 2022

So for this error:

 System.ExecutionEngineException: Attempting to JIT compile method (wrapper delegate-invoke) int <Module>:invoke_callvirt_int_MyItem (reflecBug.MyItem) while running in aot-only mode. See https://docs.microsoft.com/xamarin/ios/internals/limitations for more information.

What happens is that linq creates a Func<MyItem,int> delegate in FuncCallInstruction<T0, TRet>:.ctor () without a target, then invokes it. In mono, this is handled by creating a specialized wrapper method at runtime, which makes a
virtual call to MyItem.get_Id. The AOT compiler doesn't know that it needs to create this wrapper since the information
needed to create it is not visible in the program.

@lambdageek
Copy link
Member

Can't repro the "Expression must be readable" error from #69410 (comment) with dotnet/runtime main on MacCatalyst.

Same error as ios device:

Screen Shot 2022-06-07 at 17 00 59


I think the question is how did this work with mono/mono linq for Xamarin.Forms - it doesn't seem like this should be possible.

I think one workaround on maui might be to enable the mono interpreter as a backup to AOT.

@vargaz
Copy link
Contributor

vargaz commented Jun 7, 2022

Running with full-aot+interp fixes the ExecutionEngineException for me.

@steveisok
Copy link
Member

Running with full-aot+interp fixes the ExecutionEngineException for me.

@rolfbjarne Is this mode supported in xam iOS?

@MichalStrehovsky
Copy link
Member

What happens is that linq creates a Func<MyItem,int> delegate in FuncCallInstruction<T0, TRet>:.ctor () without a target, then invokes it

As I wrote above: "The originally reported issue ("Attempting to JIT compile method") might be related to what I called out in #61952 in my comments around iDevices/iOS: there are more codepaths in S.L.Expressions that should be activated for a full AOT experience. Some of these codepaths require extra runtime support - it's code around CanEmitObjectArrayDelegate and CanCreateArbitraryDelegates."

Specifically to avoid landing in FuncCallInstruction..ctor, you either need to:

For NativeAOT, we set CanCreateArbitraryDelegates to false.

@steveisok
Copy link
Member

@MichalStrehovsky It appears that CanCreateArbitraryDelegates is being linked out during the libraries build, so what we ship does not have this. I still hit the same problem if FEATURE_FAST_CREATE is undefined, so perhaps it's something else.

@MichalStrehovsky
Copy link
Member

@MichalStrehovsky It appears that CanCreateArbitraryDelegates is being linked out during the libraries build

This is because of this:

<!--
Disable constant propagation so that methods referenced from ILLink.Substitutions.xml don't get inlined
with a wrong value at library build time and the substitution can still be selected at publish time.
For iOS/tvOS/Catalyst we prefer smaller size by default, so keep constprop enabled to get rid of the expression compiler.
-->
<ILLinkDisableIPConstProp Condition="'$(TargetPlatformIdentifier)' != 'iOS' and '$(TargetPlatformIdentifier)' != 'tvOS' and '$(TargetPlatformIdentifier)' != 'MacCatalyst'">true</ILLinkDisableIPConstProp>

We want to run IPConstProp for iDevices because we need it to get rid of the Expressions IL Compiler, but we don't want to constant propagate the definition of CanCreateArbitraryDelegates.

Possible solutions:

  • A substitution file for iDevices library build that essentially replaces the return true to return false, or
  • #define the method to return false in the cs file since we build a special version of the library for iDevices anyway.

@rolfbjarne
Copy link
Member

Running with full-aot+interp fixes the ExecutionEngineException for me.

@rolfbjarne Is this mode supported in xam iOS?

Adding this to the csproj:

<PropertyGroup>
    <UseInterpreter>true</UseInterpreter>
</PropertyGroup>

will enable the interpreter, but still AOT every assembly (although I'm not sure if that's equivalent to "full aot").

@AntonKosenkoDX
Copy link

AntonKosenkoDX commented Jun 15, 2022

Thank you for the workaround. It works, but it's still very weird bug for us because this property can be set in application, not in libraries, which we distribute. And our customers will still run into this issue.

@jeffhandley jeffhandley added this to the Future milestone Jun 25, 2022
@tinytownsoftware
Copy link

I also confirm that this workaround works. Thank goodness!

@Cheesebaron
Copy link

How did this even work in Xamarin.iOS Apps before and started breaking targeting net6.0-ios? This is a huge regression!

@J-Swift
Copy link

J-Swift commented Nov 22, 2022

Yeah I just ran into this for a second time in a Maui app targeting net6. Its especially pernicious because in both cases the app is running just fine for months before starting to crash at runtime suddenly, after a seemingly innocent change. The access patterns before and after the crashes are largely the same, it just so happens that for whatever reason the compiler optimizes/decides slightly differently in one case vs the other. Very frustrating.

@J-Swift
Copy link

J-Swift commented Nov 22, 2022

I should also note that in the first case I ran into this, the UseInterpreter didn't fix it. I had to remove some code that ultimately generated IL.emit calls. The second time I hit it, the UseInterpreter seems to have worked, but its only been a couple days so I'm not 100% ready to mark it resolved.

@joduss
Copy link

joduss commented Mar 10, 2023

Is there any update on this regression?

It is rather a problem when migrating a Xamarin.iOS project to .Net7-iOS.
And the use of the interpreter is a dirty workaround. In our case, the interpreter severely impacts the performance of the application. (About 50-60% slower)

@tom-b-iodigital
Copy link

also impacting my application, interpreter is definetely not an option as the performance is unacceptable

@rolfbjarne
Copy link
Member

@tom-b-iodigital

Can you try this:

<PropertyGroup>
    <MtouchInterpreter>-all</MtouchInterpreter>
</PropertyGroup>

This will enable the interpreter, but still AOT compile all the assemblies. This may still fix this particular issue (because the app now has interpreter support at runtime), while keeping performance acceptable.

@rolfbjarne
Copy link
Member

What happens is that linq creates a Func<MyItem,int> delegate in FuncCallInstruction<T0, TRet>:.ctor () without a target, then invokes it

As I wrote above: "The originally reported issue ("Attempting to JIT compile method") might be related to what I called out in #61952 in my comments around iDevices/iOS: there are more codepaths in S.L.Expressions that should be activated for a full AOT experience. Some of these codepaths require extra runtime support - it's code around CanEmitObjectArrayDelegate and CanCreateArbitraryDelegates."

Specifically to avoid landing in FuncCallInstruction..ctor, you either need to:

For NativeAOT, we set CanCreateArbitraryDelegates to false.

Couldn't CanCreateArbitraryDelegates reflect RuntimeFeature.IsDynamicCodeSupported instead? That way it wouldn't have to be special-cased everywhere.

private static bool CanCreateArbitraryDelegates => RuntimeFeature.IsDynamicCodeSupported;

@MichalStrehovsky
Copy link
Member

That might work: it was recently done to one of the other S.L.Expressions feature switches as well: #80759.

@ivanpovazan
Copy link
Member

If this:

private static bool CanCreateArbitraryDelegates => RuntimeFeature.IsDynamicCodeSupported;

is the only change required, I can give it a try locally and see how it affects the app size.

@ivanpovazan
Copy link
Member

ivanpovazan commented Jun 16, 2023

I can reproduce in .net 6. Under .NET 7, the message is a bit more friendly

Expression must be readable (Parameter 'expression') (System.ArgumentException)
   at System.Dynamic.Utils.ExpressionUtils.RequiresCanRead(Expression , String , Int32 )
   at System.Linq.Expressions.Expression.Convert(Expression , Type , MethodInfo )
   at System.Linq.Expressions.Expression.Convert(Expression , Type )
   at Program.CompileLambda()
   at Program.GetValue(Object )
   at Program.Main(String[] )
   at Program.<Main>(String[] )

@akoeplinger do you know of any mono trickery that was done in the past?

@MichalStrehovsky do you have any insight into if this can even work in runtime?

The reason why the repro sample shows a different error message, than what was originally reported, is because ILLinker trims out a property in such way that it breaks the execution before it reaches the actual problem (covering it up).

This is observable during compilation with warning messages like:

warning IL2026: Using member 'System.Linq.Expressions.Expression.Property(Expression, String)' which has 'RequiresUnreferencedCodeAttribute' can break functionality when trimming application code. Creating Expressions requires unreferenced code because the members being referenced by the Expression may be trimmed.

Rooting the assembly containing the repro source code, with something like:

<ItemGroup>
    <TrimmerRootAssembly Include="<assembly-containing-the-repro>" />
</ItemGroup>

reveals the reported Attempting to JIT compile method '(wrapper delegate-invoke) issue.


PS I assume this was not visible with dotnet6 as the ILLinker got improved in the meantime.

@vargaz
Copy link
Contributor

vargaz commented Jun 21, 2023

The ExecutionEngineException is supposed to be fixed in net8 by:
#85643

@ivanpovazan
Copy link
Member

ivanpovazan commented Jun 21, 2023

The ExecutionEngineException is supposed to be fixed in net8 by:
#85643

Unfortunately, that fix does not seem to resolve the issue reported here.

I have managed to reproduce the reported issue with fc75f7e on the main branch by adapting the HelloiOS accordingly and running it on a device.

Repro steps

  1. checkout current main branch
  2. apply this patch that sets up the HelloiOS sample app: https://gist.github.com/ivanpovazan/27cc90887e9709f0bd46579f9987e996
  3. build the repo with: ./build.sh mono+libs -c debug -os ios -arch arm64
  4. got to the sample dir: cd src/mono/sample/iOS
  5. run the sample: make run TARGET=ios MONO_ARCH=arm64
  6. inspect the console output with Console.app

The console log shows:

2023-06-21 16:22:27.691562+0200 HelloiOS[11803:4577363] System.ExecutionEngineException: Attempting to JIT compile method '(wrapper delegate-invoke) int System.Func`2<MyItem, int>:invoke_callvirt_TResult_T (MyItem)' while running in aot-only mode. See https://docs.microsoft.com/xamarin/ios/internals/limitations for more information.

Full log can be found here: https://gist.github.com/ivanpovazan/0f8bda708344858a6dd8c9efbddeea6c

@ivanpovazan
Copy link
Member

I have verified locally that this has been fixed via: #88539
The repro steps provided in: #69410 (comment) are not crashing anymore and the application successfully runs.

The mentioned fix will become available in .NET8 preview 7 release.

@JaneySprings could you please confirm if this solved your problem?

@JaneySprings
Copy link
Author

JaneySprings commented Jul 17, 2023

@ivanpovazan Still reproducing on my example from this issue (net8.0-ios | release | device) 😢

image

.NET version: 8.0.100-preview.6.23330.14

Preview 7 is not available globally yet?

@ivanpovazan
Copy link
Member

@ivanpovazan Still reproducing on my example from this issue (net8.0-ios | release | device) 😢

Thank you for checking it out, but unfortunately the fix is available only in preview 7.

Preview 7 is not available globally yet?

Correct. Preview 7 is not available officially yet. It will be released early to mid August.


I have verified that the sample works with the latest nightly build: 8.0.100-preview.7.23364.32, but will keep the issue open until it is not verified that the official preview 7 release includes the fix.

@ivanpovazan
Copy link
Member

I am closing the issue as I have verified that the issue has been fixed with the official .NET8 preview 7 release.

@ivanpovazan ivanpovazan modified the milestones: Future, 8.0.0 Aug 8, 2023
@ghost ghost locked as resolved and limited conversation to collaborators Sep 8, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

No branches or pull requests