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

Compatibility with Android/iOS AOT compilation #552

Open
breenbob opened this issue Jan 10, 2023 · 9 comments
Open

Compatibility with Android/iOS AOT compilation #552

breenbob opened this issue Jan 10, 2023 · 9 comments
Labels
bug Something isn't working
Milestone

Comments

@breenbob
Copy link

breenbob commented Jan 10, 2023

Prior to the removal of the .WithoutFastExpressionCompiler rule, DryIoc used to work fine in Xamarin.Android, Xamarin.iOS, and I suspect .Net Maui Android/iOS apps also. The introduction of fast expression compilation seems (to me) to be the point at which code that depended on availability of the JIT compiler to run was introduced.

As per the documentation on limitations of Xamarin.iOS, iOS apps must use AOT compilation and as such do not support aspects of generics and reflection that depend on JIT compilation.

I have run into this issue when using the Prism.DryIoc.Maui package:

Attempting to JIT compile method 
'FastExpressionCompiler.LightExpression.ExpressionCompiler:TryCompile (FastExpressionCompiler.LightExpression.ExpressionCompiler/ClosureInfo&,
    System.Type,System.Type[],
    System.Type,FastExpressionCompiler.LightExpression.Expression,
    System.Collections.Generic.IReadOnlyList`1<FastExpressionCompiler.LightExpression.ParameterExpression>,bool)' 
while running in aot-only mode. 
See https://docs.microsoft.com/xamarin/ios/internals/limitations for more information.

The issue itself seems to stem from DryIoc and the FastCompilers packages in their dependency on these unsupported aspects.

This documentation also suggests marking methods that rely on these with one of the following attributes would bypass this problem:
UnmanagedFunctionPointerAttribute (preferred, since it is cross-platform and compatible with .NET Standard 1.1+)
MonoNativeFunctionWrapperAttribute

It states that failing to provide one of these attributes will result in a runtime error exactly like I am seeing in my iOS app above.

As an experiment I have tried forking DryIoc from v5.1.0 (version referenced by Prism.DryIoc.Maui) and re-adding the dropped commit for .WithoutFastExpressionCompiler. This was painstaking to say the least! I pushed the packed nuget for this to a private repo and referenced from there. After updating Prism.DryIoc.Maui and my app to use the updated packages, my app still crashed with a similar error to above... but it was in a different method this time, nothing to do with the fast compiler stuff:

Attempting to JIT compile method
'(wrapper delegate-invoke) DryIoc.IScope <Module>
  :invoke_callvirt_IScope_IResolverContext (DryIoc.IResolverContext)'
while running in aot-only mode.
See https://docs.microsoft.com/xamarin/ios/internals/limitations for more information.

So I am guessing DryIoc now has other areas in the code also dependent on JIT compilation added since this feature was removed that these changes alone do not cover.

Is it possible for support for AOT compilation to be added to DryIoc?

Note, best I can tell this issue does not seem to occur in Prism.Forms, which references DryIoc v4.7.7.

Any help or hints greatly appreciated!

@dadhi
Copy link
Owner

dadhi commented Jan 11, 2023

@breenbob Hi, thanks for thorough analysis.
I am struggling to find the cause before, so hopefully I will find something now with your details.

@dadhi dadhi added the bug Something isn't working label Jan 11, 2023
@dadhi dadhi added this to the v6.0.0 milestone Jan 11, 2023
@breenbob
Copy link
Author

To be honest I was aware of the limitations around AOT/generics, but I wasn't aware of those attributes until I went searching for the docs to link here. I will give them a go in my fork of 5.1.0 and let you know on here how that works out for me. Thanks for considering this issue, and the great library!

@breenbob
Copy link
Author

Theoretically, if they work, I am guessing it would mean no need to re-introduce WithoutFastExpressionCompiler functionality, but instead updating the FastExpressionCompiler library to use same annotations..

@breenbob
Copy link
Author

After reading the docs some more, I came across this:

Rules.WithUseInterpretation

The compilation (essentially `System.Reflection.Emit`) is not supported by all targets, e.g. by the Xamarin iOS. In this case you may specify to always use the interpretation via the rule: 

var c = new Container(rules => rules.WithUseInterpretation());

DryIoc uses its own interpretation mechanism which is faster than `System.Linq.Expressions.Expression.Compile(preferInterpretation: true)` because DryIoc can recognize its own internal methods in the resolved expression tree, and call them directly without reflection. It has other optimizations as well.

I knew Prism.Maui did not set this rule by default, so I tried adding it in a new branch of Prism.Maui referencing the stock v5.1.0 of DryIoc.dll from Nuget (as opposed to my fork). Pushed the Prism Nugets to private repo, referenced them in my app, did a clean/build & run in Release mode on iOS to ensure AOT compilation, and my issue is resolved!

Would like your opinion @dadhi but pretty sure that means this issue can be closed?

I will PR the fix into Prism.Maui separately.

@dadhi
Copy link
Owner

dadhi commented Jan 11, 2023

@breenbob Huh, sorry :-( I was for some reason assuming that WithUseInterpretation() is used.
Yes, this is the way to solve it.

In the related issues,
I wanted to improve the situation by recognizing the target is not supporting System.Reflection.Emit
and setting it in the container automatically.
But I did not find the way.

Maybe you will have a suggestion, as someone really working on these targets.
It may be some environment check, some reflection probe for missing property, method or class... or preferably more robust modern way?

@breenbob
Copy link
Author

Yes sorry, I didn't write the package and not really looked at this before.

I'm honestly not sure if there is anyway to detect AOT compilation, either directly or by proxy. I do know there is a csproj setting that gets enabled for this by default in Release build configurations:
<RunAOTCompilation>true</RunAOTCompilation>

But doubt there is any way to check that at runtime. Perhaps some msbuild magic...

Whether you can check for unsupported aspects using reflection, I just don't know, but because of AOT compilation coming into play again I would doubt it.

All I can suggest (for Xamarin/.Net Maui at least) is to do something like this:

#if RELEASE && (ANDROID || IOS || MACCATALYST)
defaultRules = defaultRules.WithUseInterpretation();
#endif

But that would not be fool proof, e.g. a custom Release build configuration where the standard Release constant has not been defined. We do this sometimes to have separate configs for CI vs App Store releases...

@dadhi
Copy link
Owner

dadhi commented Jan 12, 2023

@breenbob
This one looks promising:

#if RELEASE && (ANDROID || IOS || MACCATALYST)
defaultRules = defaultRules.WithUseInterpretation();
#endif

Maybe RELEASE is not required here, because why care for the compilation in the DEBUG. I think from the debugging perspective it even better to have the interpretation, because it is more consistent experience (no compilation jump).

Then the next question, how to approach it when building the DryIoc library.

  • One possibility is to build for the specific target, e.g. net6.0-ios, net6.0-android, etc.
  • The other is to provide some source-generator magic, and have this #if in the partial class Container implementation on the client side.
    The both approaches may have a problem when DryIoc is the transitive dependency of the other library, like the Prism.MAUI.

What are your thoughts on this?
What targets are you using personally, what .net?

@kyurkchyan
Copy link

I am facing this same issue. I am on the same page with @breenbob using Prism, Maui and DryIoc. @breenbob did you manage to create a PR for Prism, or perhaps you found a different way of resolving this issue?

@kyurkchyan
Copy link

For those who may come across this thread. I found a solution with Prism.Maui and DryIoc.

Luckily there's an overload of UserPrism which takes the DryIoc Rules. So, I created rules and passed them to that extension method. IOS/Android apps in Release/AOT mode are not crashing anymore.

private static Rules DefaultRules => Rules.Default.WithConcreteTypeDynamicRegistrations(reuse:Reuse.Transient)
                                                  .WithUseInterpretation();
                                                  
.....

var builder = MauiApp.CreateBuilder();
            builder
                .UseMauiApp<App>()
                .UsePrism(DefaultRules, Startup.SetupPrismApplication)
....

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

No branches or pull requests

3 participants