-
Notifications
You must be signed in to change notification settings - Fork 888
AOT Exception Patterns and Hacks
Runnning on iOS sometimes raise runtime exception.
System.ExecutionEngineException: Attempting to JIT compile method
Generics is easy to cause a problem of AOT. UniRx use generics pipeline heavyly. But we cannnot be afraid if we know a pattern and counterplan. If you can't avoid AOT problem, please report to GitHub issues.
// ExecutionEngineException: Attempting to JIT compile method '(wrapper native-to-managed)' while running with --aot-only
var a = "hoge";
Interlocked.CompareExchange<string>(ref a, "hugahuga", "hoge");
// It's Ok! CompareExchange(int, object, etc...) works.
object b = "hoge";
Interlocked.CompareExchange(ref b, "hugahuga", "hoge");
We can't use Interlocked.CompareExchange[T]. (wrapper native-to-managed) exception needs MonoPInvokeCallback(see:Unity TroubleShooting), but Interlocked.CompareExchange[T] is internal, it's in mscorlib.dll. Alterenate, we can use Interlocked.CompareExchange(object).
The Three pair kills application.
public interface IMyInterface
{
void MyMethod<T>(T x);
}
public class MyImpl : IMyInterface
{
public void MyMethod<T>(T x)
{
}
}
IMyInterface intf = new MyImpl();
intf.MyMethod("hogehoge"); // ReferenceType is not dead
// System.ExecutionEngineException: Attempting to JIT compile method 'MyImpl:MyMethod<int> (int)' while running with --aot-only.
intf.MyMethod(100); // die
We can avoid by compiler hint.
// don't need call, write specified type anywhere.
static void _CompilerHint()
{
new MyImpl().MyMethod(default(int));
}
void Awake()
{
IMyInterface intf = new MyImpl();
intf.MyMethod(100); // Struct but not dead
}
static void Death<T>()
{
var act = new Action<T>(_ => { Debug.Log("hoge"); }); // not dead yet
// System.ExecutionEngineException: Attempting to JIT compile method '<Death>b__0<int> (int)' while running with --aot-only.
act(default(T)); // die
}
// call with struct cause exception(If class run perfectly)
Death<int>();
We can avoid by capture external value.
static void Death<T>()
{
var _dummy = 0;
var act = new Action<T>(_ =>
{
Debug.Log("hoge");
_dummy.GetHashCode(); // capture external variable
});
act(default(T)); // not dead
}
Death<int>();
This technique is very useful for use UniRx because Rx(LINQ) use lambda heavily.
This is complex and seriously problem.
public static void Run()
{
// call by reference type cause exception
// System.ExecutionEngineException: Attempting to JIT compile method 'Method2<int, object> ()' while running with --aot-only.
Method1<object>();
}
public static void Method1<T1>()
{
Method2<int, T1>(); // one side as value type
}
// Two type args and has return type
static string Method2<T1, T2>()
{
return "";
}
Enum is sometimes dangerous.
public enum MyEnum
{
Apple
}
// System.ExecutionEngineException: Attempting to JIT compile method '(wrapper managed-to-managed) MyEnum[]:System.Collections.Generic.ICollection`1.CopyTo (UniRx.MyEnum[],int)' while running with --aot-only.
new[] { MyEnum.Apple }.ToArray();
We can avoid UniRx's Utility - AsSafeEnumerable.
new[] { MyEnum.Apple }.AsSafeEnumerable().ToArray();
Struct often cause storange behaviour. You can wrap IEnumerable/IObservable's element to Tuple1(as class wrapper).
Enumerable.Range(1, 10).WrapValueToClass(); // IEnumerable<Tuple<int>>
Observable.Range(1, 10).WrapValueToClass(); // IObservable<Tuple<int>>
Some LINQ methods can't work on iOS. It's not LINQ limitation because mono 2.8 already fixed(such as this patch https://github.com/mono/mono/commit/071f495d6a4ce4951e2b2c9069586bd5bcde5fbb ). But Unity's mono runtime is 2.6. I post upgrade Enumerable.cs request on Unity Feedback. Upgrade Enumerable.cs for avoid AOT Problem of LINQ(Average etc...) .I'm glad to if you vote.
Reference document is generated by neuecc/MarkdownGenerator