Search Unity

  1. Unity Asset Manager is now available in public beta. Try it out now and join the conversation here in the forums.
    Dismiss Notice

Are C# Expression Trees (or ILGenerator) allowed on iOS

Discussion in 'Experimental Scripting Previews' started by lluo, Aug 23, 2017.

  1. lluo

    lluo

    Joined:
    May 19, 2016
    Posts:
    20
    So after switching to the 2017.1 experimental .NET 4.6 profile, I could finally leverage the full power of C# expression trees in my serialization framework. However, a good question arises: are C# expression trees (and thus ILGenerator) considered dynamic code generation by iOS, and thus not really allowed for iOS publishing?

    Anyone has any insights into this?

    Cheers!
     
  2. M_R

    M_R

    Joined:
    Apr 15, 2015
    Posts:
    559
    as far as I know, everything that does dynamic code generation will either not compile or throw an exception on IL2CPP. you can test it on a build and see.

    I don't know if there is something that IL2CPP allows but is forbidden by Apple policies (short of writing your own bytecode interpreter)
     
  3. DenisZykov

    DenisZykov

    Joined:
    Mar 31, 2016
    Posts:
    19
    You can parse, compile and execute expression trees on iOS with this asset using .CompileAot() extension on System.Linq.Expression.Expression<T> instance.
     
    Last edited: Aug 23, 2017
  4. JoshPeterson

    JoshPeterson

    Unity Technologies

    Joined:
    Jul 21, 2014
    Posts:
    6,938
    The .NET 4.6 profile doesn't change the IL2CPP (and Apple restrictions). It is still an ahead-of-time compiler which does not support dynamic code generation. Basically, you cannot use anything in the System.Reflection.Emit namespace.
     
  5. DenisZykov

    DenisZykov

    Joined:
    Mar 31, 2016
    Posts:
    19
    Actually Unity team could implement IL interpreter for AOT environment. It would be in 'gray' area of Apple policy.
     
    dCalle likes this.
  6. Arkade

    Arkade

    Joined:
    Oct 11, 2012
    Posts:
    655
    Hey, link just goes to assert store front page for me...?
     
  7. DenisZykov

    DenisZykov

    Joined:
    Mar 31, 2016
    Posts:
    19
    It is strange. I just copied it from browser address bar.
    http://u3d.as/oCH <- link in old Asset Store.
     
    Arkade likes this.
  8. lluo

    lluo

    Joined:
    May 19, 2016
    Posts:
    20
    How about things under System.Linq.Expressions only? I might be a little misleading, in that I have no explicit usage of anything under the System.Reflection.Emit namespace, I exclusively use the Expression Trees (System.Linq.Expressions); but I somehow got the impression that under the hood, Expression Trees actually generates IL code during the "Compile()" phase?

    So just to make the question exactly lean and mean: are C# Expression Trees (System.Linq.Expressions) allowed for iOS?
     
  9. lluo

    lluo

    Joined:
    May 19, 2016
    Posts:
    20
    Hmm, I've actually got an idea: since my usage case doesn't really involve "dynamic code generation", even though the Expression Tree is used (with reflection) to inspect existing types and generate "logic" to process the data with the known types, it might actually be possible for me to take the compiled expression trees and create an assembly in a separate process (sort of a AOT process), and at the publishing or runtime, the generated assembly is shipped (or loaded at runtime), so there would be no runtime code generation whatsoever.

    The bits I'm looking now is: LambdaExpression.CompileToMethod based on this SO post: https://stackoverflow.com/questions...the-main-entry-point-to-a-new-executable-disk

    Please share your insights on this. Thanks again!
     
    Arkade likes this.
  10. JoshPeterson

    JoshPeterson

    Unity Technologies

    Joined:
    Jul 21, 2014
    Posts:
    6,938
    I don't think this will work. That example on Stack Overflow is using AssemblyBuilder, which is in System.Reflection.Emit. However to be sure, your best option is to try it in a small test project and see. We will do what we can to support dynamic code, as long as it does not violate an App Store restrictions.
     
    VOTRUBEC and dCalle like this.
  11. M_R

    M_R

    Joined:
    Apr 15, 2015
    Posts:
    559
    that can be done in the editor though. I have something similar for protobuf.net: I generate the DLL from my data classes via an editor MenuItem and put it into the project.
     
    MechEthan and lluo like this.
  12. JoshPeterson

    JoshPeterson

    Unity Technologies

    Joined:
    Jul 21, 2014
    Posts:
    6,938
    Indeed, the editor uses Mono JIT, so runtime code generation is fully supported.
     
  13. lluo

    lluo

    Joined:
    May 19, 2016
    Posts:
    20
    Exactly as M_R suggested, the assembly generation would be done in the editor (a separate phase), and the generated assembly would be put in the Unity project, and gets referenced from there.
     
  14. lluo

    lluo

    Joined:
    May 19, 2016
    Posts:
    20
    A follow up question: does IL2CPP automatically process an (Unity compatible) assembly that resides in the current Unity project (e.g. under "Assets/DLL" folder, so it looks like: "Assets/DLL/MyAssembly.dll")? Does IL2CPP work against "Assembly.Load(AssemblyName("MyAssembly"))"?

    Note that I'm aware that Assembly.LoadFrom(assemblyFilePath) would not work (see: https://forum.unity3d.com/threads/il2cpp-loading-c-assembly-at-runtime.417553/), but Assembly.Load(AssemblyName) seems only needing to find a "reference", and I'm assuming it should work with IL2CPP (or with an IL2CPP processed assembly)?
     
  15. lluo

    lluo

    Joined:
    May 19, 2016
    Posts:
    20
    Unfortunately, Assembly.Load(AssemblyName) does not work against IL2CPP ecosystem.

    Based on my test (IL2CPP with the Xcode iOS simulator), if an assembly is referenced explicitly (such as invoking a function as defined inside the assembly in question), IL2CPP would pick the assembly up and does all the necessary transformation, and the generated C++ code is guaranteed to be referenced correctly, while if there are no explicit references to the assembly in question, the entire assembly would be stripped, and any runtime binding to the assembly would fail (even an in memory binding would fail, if implemented at all) - similar to the C++ linker that unreferenced symbols are stripped.
     
  16. JoshPeterson

    JoshPeterson

    Unity Technologies

    Joined:
    Jul 21, 2014
    Posts:
    6,938
    @lluo:

    Yes, the behavior you see is expected. IL2CPP does not convert assemblies that are not directly used, in order to keep code size down. I would recommend using something from this assembly elsewhere in the project to get it pulled into the build.

    If you are accessing parts of the code only via reflection, you will need to use a link.xml file to prevent the managed code stripper from removing that code as well.
     
  17. lluo

    lluo

    Joined:
    May 19, 2016
    Posts:
    20
  18. Thaina

    Thaina

    Joined:
    Jul 13, 2012
    Posts:
    1,168
    I have heard that in C# recent version `Expression.Compile` has 2 implementation. It would fallback to use reflection interpreter in any platform that not support dynamic codegen

    https://stackoverflow.com/a/47476886/1513380

    If it's true then it would be useful in some case that not really need performance. Such as let user add math script to control object movement

    Would be anyone pleased to help test that is it really work on iOS and WebGL?
     
  19. JoshPeterson

    JoshPeterson

    Unity Technologies

    Joined:
    Jul 21, 2014
    Posts:
    6,938
    We don't have support for an interpreter on iOS, WebGL, or any AOT platform in Unity. The Mono team has recently been working on an interpreter that might allow for something like this in the future, but it is not on the roadmap for Unity for the time being.
     
    MechEthan likes this.
  20. Thaina

    Thaina

    Joined:
    Jul 13, 2012
    Posts:
    1,168
    According to my investigation the reflection interpreter is natively supported in .NET framework since before 4.0. If unity was brought dotnet framework as is I think it would also contain the interpreter in the 4.6 engine

    Problem is I have no iOS device to test on my own. And the API for ensure interpreter usage is just added in 4.7.1
     
  21. JoshPeterson

    JoshPeterson

    Unity Technologies

    Joined:
    Jul 21, 2014
    Posts:
    6,938
    I don't think there is a reflection interpreter in Mono now (although they are working on something like this).
     
  22. Thaina

    Thaina

    Joined:
    Jul 13, 2012
    Posts:
    1,168
    @JoshPeterson Doesn't dotnet 4.6 experimental use corefx?

    The implementation of Expression.Compile which also include reflection interpreter is in the corefx

    Could you please let me have information about what mono people trying to do about this case?
     
  23. JoshPeterson

    JoshPeterson

    Unity Technologies

    Joined:
    Jul 21, 2014
    Posts:
    6,938
    No, the Unity .NET 4.6 code is from Mono, both class libraries and run time code.

    Here are some details about the Mono interpreter: http://www.mono-project.com/news/2017/11/13/mono-interpreter/
     
  24. Thaina

    Thaina

    Joined:
    Jul 13, 2012
    Posts:
    1,168
  25. Thaina

    Thaina

    Joined:
    Jul 13, 2012
    Posts:
    1,168
    @JoshPeterson I was having conversation with mono people in gitter and this is what they answer me

     
    JoshPeterson likes this.
  26. JoshPeterson

    JoshPeterson

    Unity Technologies

    Joined:
    Jul 21, 2014
    Posts:
    6,938
    Starting in Unity 2018.2, IL2CPP will use the Interpreter for Linq expressions, so some things have a chance of working. 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:

    Code (CSharp):
    1. <linker>
    2.   <assembly fullname="System.Core">
    3.    <type fullname="System.Linq.Expressions.Interpreter.LightLambda" preserve="all" />
    4.   </assembly>
    5. </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:

    Code (CSharp):
    1. var body = Expression.Constant("Hey!");
    2. var lambda = Expression.Lambda(body);
    3. var function = lambda.Compile();
    4. 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. This is an area of future development for IL2CPP, but it is not ready for production yet.
     
    yyhvs1995, Rabadash8820 and Thaina like this.
  27. Thaina

    Thaina

    Joined:
    Jul 13, 2012
    Posts:
    1,168
    @JoshPeterson Could we see a sample of code that would run into AoT limitation?
     
  28. JoshPeterson

    JoshPeterson

    Unity Technologies

    Joined:
    Jul 21, 2014
    Posts:
    6,938
    Yes! This code won't work with IL2CPP currently

    Code (CSharp):
    1. var body = Expression.Add(Expression.Constant(42), Expression.Constant(1));
    2. var lambda = Expression.Lambda(body);
    3. var function = lambda.Compile();
    4. var result = function.DynamicInvoke();
    Those integers cause value type generics to be used, which IL2CPP cannot anticipate (since they are used via reflection), and so cannot generate code for them.
     
    Thaina likes this.
  29. Thaina

    Thaina

    Joined:
    Jul 13, 2012
    Posts:
    1,168
    Thank you. But this is really sad because math operation is what I really need for this feature
     
  30. juanfornos

    juanfornos

    Joined:
    Feb 17, 2018
    Posts:
    10
    @JoshPeterson if I understood correctly, an expression that returns a bool wouldn't work, would it?

    I'm "baking" a MethodInfo.Invoke (a function that returns a bool) with an Expression.Call to avoid allocs.

    In this scenario I'm getting:
    ExecutionEngineException: Attempting to call method 'System.Linq.Expressions.Interpreter.LightLambda::MakeRun0<System.Boolean>' for which no ahead of time (AOT) code was generated.
     
  31. JoshPeterson

    JoshPeterson

    Unity Technologies

    Joined:
    Jul 21, 2014
    Posts:
    6,938
    Yes, unfortunately this is correct. A method that returns bool will encounter the value type generics restrictions.
     
  32. juanfornos

    juanfornos

    Joined:
    Feb 17, 2018
    Posts:
    10
    Thank you for your answer.

    I read in your previous posts that in the future IL2CPP will have the capability of handling value types. Is it possible to have an estimate of when it will be released?
     
    Last edited: Feb 19, 2019
  33. JoshPeterson

    JoshPeterson

    Unity Technologies

    Joined:
    Jul 21, 2014
    Posts:
    6,938
    That is correct. Unfortunately we don't have an estimate for that work to be completed.
     
  34. Mgravitus

    Mgravitus

    Joined:
    Oct 8, 2015
    Posts:
    13
  35. JoshPeterson

    JoshPeterson

    Unity Technologies

    Joined:
    Jul 21, 2014
    Posts:
    6,938
    No, I don't have an ETA or update on this feature, sorry!
     
    Mgravitus likes this.
  36. Oruji

    Oruji

    Joined:
    Nov 5, 2012
    Posts:
    14
  37. JoshPeterson

    JoshPeterson

    Unity Technologies

    Joined:
    Jul 21, 2014
    Posts:
    6,938
    No, we don't have this on the public roadmap yet, sorry. I'm not sure when we will start work on it.
     
  38. QFSW

    QFSW

    Joined:
    Mar 24, 2015
    Posts:
    2,906
    Is there any kind of work around to this such as perhaps boxing the values?

    Essentially I'm automatically supporting operators, but primitives don't have defined operators so I was using this to handle them (whereas non primitives I can just execute the operator method)
     
  39. sebas77

    sebas77

    Joined:
    Nov 4, 2011
    Posts:
    1,644
    Hi, resuming this.

    Where can I read more about System.Linq.Expressions.Interpreter.LightLambda? Am I supposed to use it directly in code? Also @JoshPeterson maybe you can tell me what's going on here: https://pastebin.com/raw/vJYMdzSK (this is a bit OT as it's NOT IL2CPP, it happens with mono)
     
    Last edited: Jan 1, 2020
  40. JoshPeterson

    JoshPeterson

    Unity Technologies

    Joined:
    Jul 21, 2014
    Posts:
    6,938
    I'm not too familiar with this code from the class libraries, so I'm not sure I suggest a good resource.

    Regarding the specific code in that link, if it works in the editor, it should work the same way in the Mono player. I'm not sure what that exception means, but can you submit a bug report with this code so that we can take a look at it and get back to you with the results?
     
  41. sebas77

    sebas77

    Joined:
    Nov 4, 2011
    Posts:
    1,644
    I just realised that the exception does say System.Linq.Expressions.Interpreter.LightLambda, so LightLamba must be an internal type. Are you able to tell me if the code in the pastebin would work with IL2CPP? It dynamically generates new IL, so it shouldn't work in an AOT scenario, but then I am not sure what I read in this thread (talking about: https://forum.unity.com/threads/are...generator-allowed-on-ios.489498/#post-3665089)
     
  42. JoshPeterson

    JoshPeterson

    Unity Technologies

    Joined:
    Jul 21, 2014
    Posts:
    6,938
    I think that it will work with IL2CPP if T is a reference type, not a value type. IL2CPP fails back to an interpreter for this code (which is implemented in the Mono class library code). So there is a chance it will work. However, that interpreter cannot handle value type generic parameters well, since it still bumps up against the IL2CPP AOT restriction where all value type generic arguments need to be known at compile time.

    I think your best option is to try it with IL2CPP.
     
  43. sebas77

    sebas77

    Joined:
    Nov 4, 2011
    Posts:
    1,644
    thanks I will just for curiosity. For IL2CPP I actually fall back to a simple cast, as it seems it doesn't incur into boxing on that platform. The real issue remains the .net standard 2.0 crash, I will open a bug in case.
     
  44. QFSW

    QFSW

    Joined:
    Mar 24, 2015
    Posts:
    2,906
    I'm getting a NullReferenceException when using this with high stripping enabled
    Code (CSharp):
    1. NullReferenceException: Object reference not set to an instance of an object
    2.   at System.Linq.Expressions.Interpreter.LightLambda.MakeRunDelegateCtor (System.Type delegateType) [0x00190] in <a52579e0afd14d309e8c16ce7ae6c478>:0
    3.   at System.Linq.Expressions.Interpreter.LightLambda.GetRunDelegateCtor (System.Type delegateType) [0x00023] in <a52579e0afd14d309e8c16ce7ae6c478>:0
    4.   at System.Linq.Expressions.Interpreter.LightLambda.MakeDelegate (System.Type delegateType) [0x00000] in <a52579e0afd14d309e8c16ce7ae6c478>:0
    5.   at System.Linq.Expressions.Interpreter.LightDelegateCreator.CreateDelegate (System.Runtime.CompilerServices.IStrongBox[] closure) [0x00012] in <a52579e0afd14d309e8c16ce7ae6c478>:0
    6.   at System.Linq.Expressions.Interpreter.LightDelegateCreator.CreateDelegate () [0x00000] in <a52579e0afd14d309e8c16ce7ae6c478>:0
    7.   at System.Linq.Expressions.LambdaExpression.Compile (System.Boolean preferInterpretation) [0x0000b] in <a52579e0afd14d309e8c16ce7ae6c478>:0
    8.   at System.Linq.Expressions.LambdaExpression.Compile () [0x00000] in <a52579e0afd14d309e8c16ce7ae6c478>:0
    Code (CSharp):
    1. ParameterExpression leftParam = Expression.Parameter(type, "left");
    2.             ParameterExpression rightParam = Expression.Parameter(type, "right");
    3.             BinaryExpression body;
    4.  
    5.             try
    6.             {
    7.                 body = PrimitiveExpressionGenerator(leftParam, rightParam);
    8.             }
    9.             catch (InvalidOperationException)
    10.             {
    11.                 return null;
    12.             }
    13.  
    14.             Delegate expr = Expression.Lambda(body, true, leftParam, rightParam).Compile();
    15.  
    16.             return new DynamicBinaryOperator(expr, type, type, type);

    Any ideas what I need to preserve with the link.xml to stop this from happening @JoshPeterson? It's being used to add together primitive values
     
  45. JoshPeterson

    JoshPeterson

    Unity Technologies

    Joined:
    Jul 21, 2014
    Posts:
    6,938
    Does this error not occur when strip engine code is disabled? I'd be surprised if strip engine code has any impact on this code, as this is in the class libraries, which strip engine code should not touch.

    Can you give an example of the C# code that causes this issue?
     
  46. QFSW

    QFSW

    Joined:
    Mar 24, 2015
    Posts:
    2,906
    Not engine code stripping but the managed stripping option
    It works in Editor, Disabled
    But throws in Low, Medium, High
    Tested on 2019.3.0f4 (if you believe it is a beta bug then I can move to there and repro it)

    The code I included in my spoiler is the c# code that uses the expression tree and it works without the stripping on, or would you like something else?

    EDIT: wanted to add this is the identical error that I would get on IL2CPP (without my own error handling)
     
  47. JoshPeterson

    JoshPeterson

    Unity Technologies

    Joined:
    Jul 21, 2014
    Posts:
    6,938
    Sorry, I misread your original post.

    Will this link.xml help (from earlier in this forum thread)?

    <linker>
    <assembly fullname="System.Core">
    <type fullname="System.Linq.Expressions.Interpreter.LightLambda" preserve="all" />
    </assembly>
    </linker>
     
    Thaina likes this.
  48. QFSW

    QFSW

    Joined:
    Mar 24, 2015
    Posts:
    2,906
    That did the trick, thanks!
     
    JoshPeterson likes this.
  49. Nanorock

    Nanorock

    Joined:
    Dec 2, 2012
    Posts:
    41
    Hi there, I'm running into this AOT issue too with Expression trees.
    Could the link.xml tell the compiler to generate the use cases ?
    If not, is there any other way ? Another file or a console argument ^^'

    We solved this issue in Newtonsoft Json by using a branched version allowing us to generate use cases (AotHelper).
    But we can't here as we have no access in Expressions.Interpreter.LightLambda ~~
     
  50. JoshPeterson

    JoshPeterson

    Unity Technologies

    Joined:
    Jul 21, 2014
    Posts:
    6,938
    Unfotunatelty, the link.xml can only be used to preserve existing types. It will not tell IL2CPP to generate new ones.