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

Unity's IL2CPP // AOT compilation mode compatibility #13099

Closed
tinanigro opened this issue Aug 23, 2018 · 34 comments
Closed

Unity's IL2CPP // AOT compilation mode compatibility #13099

tinanigro opened this issue Aug 23, 2018 · 34 comments
Labels
closed-out-of-scope This is not something that will be fixed/implemented and the issue is closed. customer-reported

Comments

@tinanigro
Copy link

tinanigro commented Aug 23, 2018

Hi! ✋

Context

We've been using EF Core in our Unity3D app for more than a year. In the Unity Editor (which runs .NET 4.6) EF Core 1.1.3 works great, as long as we manually add the required .NET dependencies. The UWP export (HoloLens in our case) doesn't work OOTB because the Unity's ReferenceRewriter executable fails find some dependencies. We worked around the problem by adding the EF Core DLLs later in the build pipeline, in the UWP project generated by Unity; this way the RefRewriter doesn't see any EFCore bits and doesn't fail the build. A bit of a 'hack', but hey it works. That's how we managed to use EFCore on Unity targeting UWP.

Issue

Now Unity is forcing developers to migrate to their IL2CPP scripting backend as they are about to drop .NET Scripting Backend support. Also, Unity supports .NET Standard 2 and the latest UWP SDK only in IL2CPP mode, so we basically have to migrate to IL2CPP.
However, it seems that EF Core 2.0 and IL2CPP/AOT do not go well together.

Here we provide two ways to reproduce the problem. The first one might give more information about the issue, but the second one is much faster to reproduce, though the logs won't tell you anything meaningful.

Steps to reproduce

1. By targeting iOS

Tested from May to June 2018

Details

  • Unity v2018.2.*
  • iOS player enabled
  • IL2CPP scripting backend
  • .NET Standard 2.0 compatibility
  • Entity Framework Core 2.* with SQLite provider (also tested with InMemory for testing, IIRC)

iOS forbids code generation and IIRC that was the primary reason Unity worked on their IL2CPP system. But we rapidly noticed that this AOT compiler can't manage to build EFCore properly as it seems to heavily rely on runtime code generation and reflection.

When trying to make EFCore work on iOS (which means by using IL2CPP), we ran into all sorts of exceptions. Here are a few of them:

NotSupportedException: /Users/builduser/buildslave/unity/build/External/il2cpp/il2cpp/libil2cpp/icalls/mscorlib/System.Reflection.Emit/AssemblyBuilder.cpp(20) : Unsupported internal call for IL2CPP:AssemblyBuilder::basic_init - System.Reflection.Emit is not supported.


Rethrow as TypeInitializationException: The type initializer for 'Microsoft.EntityFrameworkCore.Metadata.Internal.ClrAccessorFactory<Microsoft.EntityFrameworkCore.Metadata.Internal.IClrPropertySetter>' threw an exception.


ExecutionEngineException: Attempting to call method ‘Microsoft.EntityFrameworkCore.Metadata.Internal.ClrPropertySetterFactory::CreateGeneric’ for which no ahead of time (AOT) code was generated.

If necessary, I might be able to provide more detailed callstacks.
We tried many different ways to make IL2CPP compile EFCore properly but as you can see, we couldn't. One way or another, something fails, whether it's the lack of generated code ahead of time, or a forbidden API (System.Reflections.Emit), or something else.

2. In the Editor

Tested and verified a week ago

Details

  • Unity v2018.2.4
  • WSA (UWP) player enabled
  • IL2CPP scripting backend
  • .NET Standard 2.0 compatibility
  • UWP SDK 17134 (or close)
  • Entity Framework Core 2.* with and without SQLite provider

Steps

  • Create a .NET Standard 2 DLL "Test" and add the EntityFramework Core nuget package. We made sure there's some EFCore actually shipped with the DLL by adding a dummy class inheriting from DbContext and adding a few test DbSets ...
  • Build project and copy the DLL in Unity's Asset/Plugins folder
  • Go back to Unity, which will take some time to process the DLL, as it always does when a DLL is updated
  • This is what the Unity console says

Unloading broken assembly Assets/x64/Test.dll, this assembly can cause crashes in the runtime

This behaviour happens with some libraries such as Autofac and, as said, EFCore. The DLL works fine if EFCore nuget package is not installed in the "Test" DLL.

Question(s)

  • Is it known that Entity Framework Core doesn't work in AOT compilation mode, like NewtonSoft.Json and other libraries? Or it is supposed to work (I think I saw devs running EFCore on Xamarin on iOS, which would mean AOT, right? 🤔) and we're doing it wrong?
  • If it doesn't currently work, is there any plans to provide an AOT (or IL2CPP) compatible version of Entity Framework Core? Many use Unity3D and many use EFCore, and I'm actually surprised I haven't seen many talk about this issue until now, which makes me think (and hope) we're doing some things wrong.

In the meantime, I'm also preparing to open a ticket on Unity's support. I honestly still don't know if the problems stands on EFCore's part, Unity's, or ours.

Thank you for helping!

@tinanigro tinanigro changed the title Unity IL2CPP & AOT compilation mode compatibility Unity's IL2CPP // AOT compilation mode compatibility Aug 23, 2018
@ajcvickers
Copy link
Member

@ThomasNigro EF Core uses a large amount of dynamically built and compiled code. This causes issues for AOT compilers in a couple of ways:

  • Metadata and Reflection are inherent to the way EF works, which means that tree shakers and the removal of metadata from assemblies needs to be controlled to not remove things that EF needs. Issue Xamarin requires linker hints to preserve APIs accessed via reflection #10963 is tracking this for Xamarin.
  • If the execution environment does not allow running dynamic code, then EF will not work. Some environments, like .NET Native, allow the expression compiler to "work" in that it will allow the code to be "compiled" and then executed, except that the code is really never compiled, just run in an interpreter. Given that the reason for building and compiling code dynamically in the first place is performance, it can be seen that running this code in an interpreter will cause perf issues.

Neither of these things are, conceptually, blockers for running EF in an AOT environment. The first issue is just about making the configuration experience work. The second issue is not because of AOT, but because the environment precludes a JIT for also dynamically running code. However, given that the two things are often linked--AOT and removal of JIT--it does cause a major problem for EF in these systems.

Also, I should mention that EF does not dynamically create types or assemblies. (That is, it doesn't use Ref.Emit directly.) It only uses compiled expression trees and delegate creation to existing methods. This is why it works with .NET Native, at least with interpreted IL.

I don't have any context on the Unity system or how it fits in with this; we will discuss as a team whether we can put resources onto any investigation. Hopefully this general information is useful in the meantime.

@divega
Copy link
Contributor

divega commented Aug 30, 2018

@ThomasNigro just to reiterate what @ajcvickers already mentioned, instead of using Reflection.Emit directly, EF Core uses compiled LINQ expression trees, and there is an interpreter of LINQ expressions that is designed for AOT platforms.

This is what allows EF Core to work in some other AOT platforms like UWP (when compiled with .NET Native). In fact, Mono has included this interpreter of LINQ expression for a while. That is why it can work also on Xamarin for iOS (although there are some other known limitation that may be currently blocking EF Core from working on Xamarin iOS, as far as I know they are unrelated with the usage of , compiled LINQ expressions).

That said, from discussions like https://forum.unity.com/threads/are-c-expression-trees-or-ilgenerator-allowed-on-ios.489498/, it is unclear if Unity is taking advantage of it when using IL2CPP. Although the existence of the interpreter was called out near the end of the thread, Unity people in that thread seemed to be mostly unaware of this possibility.

As @ajcvickers there are also common issues caused by the removal of reflection metadata and library code that cannot be statically determined to be required (in the case of IL2CPP this is equivalent to the Unused Bytecode Stripper), but other AOT environments provide workarounds that allow controlling what code and what metadata needs to be preserved (see #10963 for more details) and so this are not blockers either.

We would love to see any gaps addressed to make it possible to use EF Core with IL2CPP, but it seems that most of the investigation needs to happen on the Unity side.

If you haven't already, I suggest to start a conversation in the Unity community forums or report this in the Unity issue tracker. Feel free to point me to it once you have it, so we can learn more details.

@divega
Copy link
Contributor

divega commented Aug 30, 2018

@ThomasNigro from reading into https://docs.unity3d.com/Manual/IL2CPP-BytecodeStripping.html, it seems that IL2CPP supports the same link.xml format for fine control of what code is removed as Xamarin does with the Mono linker, so if the first error you are getting is due to the removal of such code or reflection metadata, tweaking link.xml might help get further.

@tinanigro
Copy link
Author

tinanigro commented Aug 31, 2018

Thank you for these very detailed answers! Those really help understand the potential underlying issues.

To sum up:

  • EF Core does not actually Ref.Emit contrary to my original understandings
  • EF Core should work on AOT environment, like Xamarin and UWP (because of .NET Native) because (among other things) it is able to run a custom interpreter of LINQ expressions
  • Mono has this interpreter
  • Unity & IL2CPP might not use it.
    • That wouldn't be surprising to me because IIRC they use a very custom version of Mono that might not have have the complete AOT configuration.

Also:

  • regarding IL2CPP's Bytecode stripping, when we ran our first tests on iOS we did tweak the link.xml file (although we might have done it wrong).
  • I understand that link.xml can be used to prevent various issues with Reflection however as far as I can tell editing this file won't have any impact in the static analysis of any DLL depending on EFCore in the Unity Editor

As you suggested, I will open a ticket on the Unity issue tracker in the coming days and paste the link here right after that.

Thank you for helping and investigating on the matter!

@divega
Copy link
Contributor

divega commented Aug 31, 2018

Thank you. Looking forward to learn more about this.

  • Unity & IL2CPP might not use it.
    That wouldn't be surprising to me because IIRC they use a very custom version of Mono that might not have the complete AOT configuration.

This should be quite easy to test in a simple program without using EF Core. I will try to do some experiments.

  • I understand that link.xml can be used to prevent various issues with Reflection however as far as I can tell editing this file won't have any impact in the static analysis of any DLL depending on EFCore in the Unity Editor

Not sure if we are saying the same thing, but link.xml is supposed to help guide the stripping process. E.g. you add a type with preserve="all" when that class is required but cannot be determined statically to be used, e.g. because you only use it through reflection. As stated in the Unity documentation:

Stripping depends highly on static code analysis and sometimes this can’t be done effectively, especially when dynamic features like reflection are used. In such cases, it is necessary to give some hints as to which classes shouldn’t be touched.

@divega
Copy link
Contributor

divega commented Sep 1, 2018

I can confirm that the expression interpreter does not work with IL2CPP, however I did not observe a runtime exception. I used a simple project with this C# script:

using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq.Expressions;
using UnityEngine;
using UnityEngine.UI;

public class NewBehaviourScript : MonoBehaviour 
{
    public Text text;
    // Use this for initialization
    void Start() 
    {
        text.text = "Ok, let's try...";
        // this seems to be ignored when using IL2CPP:
        text.text ="Hey 1+1 is " + EvalExpression(() => 1 + 1); 
    }

    static int EvalExpression(Expression<Func<int>> expression)
    {
        return expression.Compile().Invoke();
    }

    // Update is called once per frame
    void Update () 
    {

    }
}

@divega
Copy link
Contributor

divega commented Sep 1, 2018

Next thing I would like to check if I have a chance if is the behavior is due to the code being removed, so I will look into adding link.xml.

@tinanigro
Copy link
Author

Regarding link.xml it seems we're saying the same thing 👌

Your script tests IL2CPP (in)ability to use the expression interpreter. During our tests with EF Core, we observed compile-time exception. I'm building a small repro project, will upload in a couple of hours.

@tinanigro
Copy link
Author

tinanigro commented Sep 4, 2018

Just added the repro sample here : https://github.com/ThomasNigro/EFCoreUnityIL2CPP
There's a README attached. Currently posting in the Unity forums.
Please let me know if you have any questions, if I forgot to add something, etc.

@tinanigro
Copy link
Author

Here is the Unity issue ticket 1077758 : https://fogbugz.unity3d.com/default.asp?1077758_tta7bdan262g8pa1

@divega
Copy link
Contributor

divega commented Sep 4, 2018

Thanks @ThomasNigro. Looking forward to answers to your ticket. Based on the error you reported, it seems there may be multiple issues preventing EF Core form working on Unity.

FWIW, I tried a few things on link.xml but didn't see any change.

@joshpeterson
Copy link

joshpeterson commented Sep 11, 2018

Hey, I'm a developer at Unity working on IL2CPP. Hopefully I can shed a bit of light on this issue.

Starting in Unity 2018.2, IL2CPP will use the Interpreter for Linq expressions, so some things have a chance of working. As discussed here, you will need to use a link.xml file, since the interpreter implementation uses reflection in internally, and the managed byte code stripper is a bit too aggressive with it.

This link.xml should be enough:

<linker>
  <assembly fullname="System.Core">
    <type fullname="System.Linq.Expressions.Interpreter.LightLambda" preserve="all" />
  </assembly>
</linker>

In Unity 2018.2, you'll need to use the .NET Standard 2.0 Api Compatibility Level option in Unity. In Unity 2018.3 we're also supporting this with the .NET 4.x Api Compatibility Level. In the future Unity 2019.1 version, you won't need the link.xml file work around, the byte code stripper will be smart enough to keep this code around when it it used.

With that said, you might still hit some limitations of the IL2CPP AOT engine. Specifically, these are often related to the use of value types. So a snippet like this, which uses only reference types (a string), will work:

var body = Expression.Constant("Hey!");
var lambda = Expression.Lambda(body);
var function = lambda.Compile();
var result = function.DynamicInvoke();

But code which uses value types (like int or double) might run into AOT limitations. This occurs because IL2CPP shares implementations of generic type with reference type arguments, and so it can do a good bit of "runtime code generation" in these cases, since the only difference is the metadata, not the code.

IL2CPP does not have the capability to share generics with value type arguments yet, where Xamarin for iOS does. This is an area of future development for IL2CPP, but it is not ready for production yet.

Regarding the specific issues raised in the bug 1077758, I'm unsure about the cause of those errors. We'll need to investigate that bug report further.

@divega
Copy link
Contributor

divega commented Sep 27, 2018

Note for triage: Clearing up assignment and milestone. I think this is not necessarily actionable in 3.0 and can move to the backlog. If things begin working better and the customer demand for running EF Core on Unity becomes significant, we can do some testing, samples, etc.

@ajcvickers ajcvickers added this to the Backlog milestone Sep 28, 2018
@QuentinBrun2
Copy link

QuentinBrun2 commented Dec 18, 2018

Hi,

I have read carefully this thread as I would like to make EFCore work with Unity IL2CPP on android & ios. I succeeded in making it working with mono backend but I am not able to compile with IL2CPP. I am using Unity 2018.3, EFCore 2.2.0. I have done a custom implementation of SQLiteRawPCL provider for android working on mono. Even without Entity framework SQLite provider, I am not able to compile.
Before adding more information about my project and issues, I would like to know if someone have succeeded in making EFCore working with Unity IL2CPP?
Thanks

@Noemata
Copy link

Noemata commented Jan 20, 2019

You can make EFCore work by having that part of the code reside inside the "GameEngine.DLL" Launcher and wiring up callbacks to both sides of the IL2CPP/C# divide. This is an under utilized capability at the moment; the ability to have code reside in the Launcher layer. Leveraging loose code coupling, you could have just your game logic code reside on the IL2CPP side with everything else sitting in the Launcher layer. Putting too much code in the Launcher layer will limit the viability of the editor experience unless you also implement a strategy to allow the editor to call out to your Launcher layer code (that too is doable). There may also be callback performance, threading performance and thread locking considerations when opting to put code in the Launcher layer. For things that just work on the IL2CPP side, it's not worth the added complexity to put that code in the Launcher layer. The UWP C# Native compiler is much better than IL2CPP in transforming IL. That too may factor into which code you choose to put into the Launcher layer.

@bricelam
Copy link
Contributor

cc @indiesaudi

@AndriySvyryd
Copy link
Member

#24903 would help with this if we are able to do it.

@Noemata
Copy link

Noemata commented May 31, 2021

@bricelam & @AndriySvyryd , let's make it happen! IL2CPP should fully support .Net Standard, else Unity will start to contribute toward .Net fragmentation in a big way. Especially if the burst compiler bits start to get more traction, with its subset of C#. There's really no need for yet another C# variant if the core can be made to work everywhere in a performant way that significantly outpaces the Unity Burst compiler.

Microsoft doesn't yet fully appreciate the extent to which Unity will fragment .Net if its not brought back into the family.

@hez2010
Copy link
Contributor

hez2010 commented Aug 14, 2021

After NativeAOT implemented generic default interface method, I successfully made efcore work with AOT. Here is a sample project for everyone wants to use efcore with NativeAOT: https://github.com/hez2010/EFCore.NativeAOT

But efcore still has a long way to go in NativeAOT aera, e.g. dynamic member annotations so that we no longer need to investigate heavely on the rd.xml.

@roji
Copy link
Member

roji commented Aug 14, 2021

@hez2010 thanks for that, good to know! We do plan to revisit making EF Core more trimming- and AOT-friendly at some point, and to ideally work without an rd.xml.

@Sahasrara
Copy link

Is there any way to use Microsoft.Data.Sqlite with Unity today or is that a ways off?

@AndriySvyryd
Copy link
Member

@Sahasrara You'd need to create a rd.xml file for it, similar to what @hez2010 did. As a starting point you can look at what we tried before and removed in c651888

Our expectation is that once Unity moves to .NET 6 we won't need to do anything special in EF or Sqlite to make them work.

@Sahasrara
Copy link

Thanks @AndriySvyryd! So if I build this repo with c651888 reverted, the generated artifacts might allow me to use Microsoft.Data.Sqlite in Unity? Sorry, I think I may be coming in from a position of significant ignorance.

@AndriySvyryd
Copy link
Member

@Sahasrara No, you can create the rd.xml file in your project, it contains instructions on what code shouldn't be removed by trimming. IL2CPP does something similar with code stripping, but is configured by link.xml.

Also, you wouldn't be able to use EF Core 6 or 7 as Unity only supports up to .NET Standard 2.1 currently. So, you'd need to use EF Core 5.

@bricelam
Copy link
Contributor

bricelam commented Oct 24, 2022

Just using Microsoft.Data.Sqlite (i.e. without EF Core) should work fine with Unity's IL2CPP today. IIRC, it doesn't even require a link.xml file. But installing the NuGet packages into a Unity project is a bit tricky--see ericsink/SQLitePCL.raw#347 (comment).

@ajcvickers
Copy link
Member

Closing since Unity is moving to .NET.

@ajcvickers ajcvickers closed this as not planned Won't fix, can't repro, duplicate, stale Oct 26, 2022
@ajcvickers ajcvickers added closed-out-of-scope This is not something that will be fixed/implemented and the issue is closed. and removed type-enhancement help wanted This issue involves technologies where we are not experts. Expert help would be appreciated. blocked propose-close area-platform labels Oct 26, 2022
@ajcvickers ajcvickers removed this from the Backlog milestone Oct 26, 2022
@emrys90
Copy link

emrys90 commented Mar 7, 2023

I'm running into the same issue, and could use some help. I get this error:

ExecutionEngineException: Attempting to call method 'Microsoft.EntityFrameworkCore.Metadata.Internal.ClrPropertySetterFactory[[Microsoft.EntityFrameworkCore.Metadata.IClrPropertySetter, Microsoft.EntityFrameworkCore, Version=3.1.18.0, Culture=neutral, PublicKeyToken=adb9793829ddae60]]::CreateGeneric<ProjectCards.Data.ServerDatabaseContext,Microsoft.EntityFrameworkCore.DbSet`1[[ProjectCards.Data.Referral, Assembly-CSharp, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null]],Microsoft.EntityFrameworkCore.DbSet`1[[ProjectCards.Data.Referral, Assembly-CSharp, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null]]>' for which no ahead of time (AOT) code was generated.

I've tried adding it to a link.xml file like this:

       <assembly fullname="Microsoft.EntityFrameworkCore" preserve="all">
              <type fullname="Microsoft.EntityFrameworkCore.Metadata.Internal.ClrPropertySetterFactory`1" preserve="all">
                     <method signature="Microsoft.EntityFrameworkCore.Metadata.IClrPropertySetter CreateGeneric`3(ProjectCards.Data.ServerDatabaseContext,Microsoft.EntityFrameworkCore.DbSet`1,Microsoft.EntityFrameworkCore.DbSet`1)" preserve="all" />
              </type>
       </assembly>

I've also tried using the AotHelper from JSON.NET like this:

        [Preserve]
        private void ConfigureAot()
        {
            this.Log("Configuring AOT");
            AotHelper.EnsureType<ClrPropertySetterFactory_AOT>();
        }

        [Preserve]
        private class ClrPropertySetterFactory_AOT : ClrPropertySetterFactory
        {
            [Preserve]
            public ClrPropertySetterFactory_AOT()
            {
                CreateGeneric<ServerDatabaseContext, DbSet<ProjectCards.Data.Referral>, DbSet<ProjectCards.Data.Referral>>(null, null);

                throw new NotImplementedException();
            }
        }

Neither approach worked though. Any ideas what I'm doing wrong?

@joshpeterson
Copy link

Neither approach worked though. Any ideas what I'm doing wrong?

Sometimes these AOT issues are very difficult to get right. I don't think the link.xml will help, as this is probably not a code stripping issue. It looks like you are on the right track with the AotHelper, but I don't see quite what is wrong.

I'll suggest another option - In Unity 2022.2 and later this should "just work" as we now have a fall back for these unseen generic cases. See https://blog.unity.com/engine-platform/il2cpp-full-generic-sharing-in-unity-2022-1-beta for details.

Is that an option for you?

@emrys90
Copy link

emrys90 commented Mar 7, 2023

Is that an option for you?

Unfortunately not. This is a game in production on Unity 2020.3, so updating to a non LTS isn't really viable (EF Core would be used on the multiplayer servers only though, but it all is built from the same Unity project).

I suspect AotHelper is failing because it considers the method signature different once its a derived class. I can't try it from the base class since the CreateGeneric method is protected.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
closed-out-of-scope This is not something that will be fixed/implemented and the issue is closed. customer-reported
Projects
None yet
Development

No branches or pull requests