Efcore: Unity's IL2CPP // AOT compilation mode compatibility

Created on 23 Aug 2018  Â·  21Comments  Â·  Source: dotnet/efcore

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!

area-unity customer-reported help wanted type-enhancement

Most helpful comment

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.

All 21 comments

@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 #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.

@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 https://github.com/aspnet/EntityFrameworkCore/issues/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.

@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.

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!

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._

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:

``` c#
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 () 
{

}

}

```

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.

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.

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.

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

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.

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.

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.

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

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.

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.

@joshpeterson I'm implementing a library that has value type arguments... I'm trying to determine if I should pay the people who have developed this library to change it so that it no longer uses value type arguments - vs waiting for Unity to fix this issue (which I assume will happen as some point in the future)... what's the best way to get some support information from Unity on when (if?) this issue will be fixed by Unity?

@josephnarai I'd recommend you request a work around. Although generic sharing for value types is still on our roadmap, it is not a feature that I expect to be implemented soon, and it will likely not be back ported to previous versions of Unity.

@joshpeterson Thanks for getting back to me so quickly. We are happy to use latest version of Unity (our app itself is not very complex), and we can get around deploying to iPad for around 6 months... was hoping I could avoid the extra cost of developing a work around if something would be ready in that timescale. But I'll look into the work around.

If things begin working better and the customer demand for running EF Core on Unity becomes significant, we can do some testing, samples, etc.

I'd just like to add my strong interest in getting this working with Unity3D and IL2CPP (since that's what Unity wants us to use going forward). I've tried building a separate project with dotnet and adding the sqlite provider, then copying the DLL's over, but I'm running into tons of dependency issues. And I think even if I resolve those I will have the same issues with IL2CPP as above.

Efcore sounds really nice and I really wish I could use it in my Unity project. I'd be very willing to assist in this endeavor anyway I can, although my experience with C# and the build ecosystem is limited.

Some of this work may overlap with #10963.

Our current plan is to focus on the .NET 5 linker/aot technology and hope it gets adopted by all .NET platforms like ASP.NET, Xamarin, UWP, and maybe even Unity someday. This was my favorite slide from dotNETConf last November:

dotnet5

In the meantime, we'd happily accept any well-written PRs that improve the IL2CPP (and overall Unity) experience with EF Core.

cc @indiesaudi

Was this page helpful?
0 / 5 - 0 ratings