Applications running on iOS cannot generate and dynamically execute code – that means no JIT (just-in-time) compiled code can run on iOS. For this reason, games for iPhone and iPad made with Unity are compiled using the AOT (ahead-of-time) compiler. Unfortunately, this means by the time the code is compiled, the compiler has much less information than the JIT compiler would have had, and as a result, it sometimes misses code that it should generate — code that your program will call. (Xamarin list the limitations on their site.) When your program calls this code on the device (not the simulator), it crashes with this error:
System.ExecutionEngineException: Attempting to JIT compile method ‘SomeMethod(SomeParameters)’ while running with –aot-only.
This often happens with generic code that uses value types as type parameters, and more so for classes used internally. For example, you can construct a Dictionary
with value type keys, but dictionaries use equality comparers internally, and these gives you problems when you try to add or access elements.
We have stumbled across this quite a few times in our Grids library, but so far, we managed to find workarounds for all of them. Here are the solutions we used.
Using Dictionary and HashSet with value type keys
Construct the collection with an appropriate IEqualityComparer
. (This is a tip from Xamarin)
If you have a Point
struct, and want to use it as keys in a Dictionary
or HashSet
, then you need to define a comparer like this:
public class PointComparer : IEqualityComparer { public bool Equals(Point p1, Point p2) { return p1.Equals(p2); } public int GetHashCode(Point p) { return p.GetHashCode(); } }
(Normally you would override Equals
and GetHashCode
for structs, to avoid using the default generated ones that are apparently quite slow.)
You can then construct your Dictionary
like this to make sure it uses this comparer:
var dict = new Dictionary(new PointComparer());
Compiler Hints
You can get the compiler to generate code if you call it explicitly, with the correct type. For example, your application crashes and complains it could jit the method bool MyMethod<Point>(Point p)
, then all you have to do is call it somewhere, and make sure it does not get optimized out. I usually do it in a method called CompilerHints
, that always returns true in a non-obvious way. (Otherwise, the clever compiler may well skip over the method and not generate the code after all).
public static bool CompilerHints() { var point = new Point(5, 5); bool v = MyMethod(point); return v || point.X == point.Y; //uses a check that the compiler cannot figure out will always be constant }
Hinting can be a tricky and tedious business: you normally have to add hints one-by-one as you discover which methods are needed. Also, figuring out how to construct instances of certain classes can be difficult, especially for scaffolding classes that are used internally. In some cases, it seems a new type is generated dynamically, in which case no hint will work. This is the case for the ordinary List
.
Using value types in Lists
Any List
method that needs to compare elements will crash on iOS. Unfortunately, you cannot specify the class that is used for this, as it is dynamically created. I couldn’t find a way to hint my code into compilation.
Instead, I rolled out my own list that delegates most tasks to an ordinary list, bit takes over any tasks that requires comparisons. Below is my implementation.
public class PointList : IList { private readonly List points; public PointList() { points = new List(); } public PointList(IEnumerable collection) { points = new List(collection); } public PointList(int capacity) { points = new List(capacity); } public IEnumerator GetEnumerator() { return points.GetEnumerator(); } IEnumerator IEnumerable.GetEnumerator() { return points.GetEnumerator(); } public void Add(Point point) { points.Add(point); } public void Clear() { points.Clear(); } public bool Contains(Point point) { return points.Any(x => x.Equals(point)); } public void CopyTo(Point[] array, int arrayIndex) { points.CopyTo(array, arrayIndex); } public bool Remove(Point point) { int index = points.FindIndex(x => x.Equals(point)); if (index >= 0) { points.RemoveAt(index); return true; } return false; } public int Count { get { return points.Count; } } public bool IsReadOnly { get { return false; } } public int IndexOf(Point point) { return points.FindIndex(x => x.Equals(point)); } public int LastIndexOf(Point point) { return points.FindLastIndex(x => x.Equals(point)); } public void Insert(int index, Point point) { points.Insert(index, point); } public void RemoveAt(int index) { points.RemoveAt(index); } public Point this[int index] { get { return points[index]; } set { points[index] = value; } } public void RemoveAll(Predicate match) { for (int i = points.Count - 1; i >= 0; i--) { if (match(points[i])) { points.RemoveAt(i); } } } }
If you use LINQ, it is useful to write a custom ToList
extension that will generate these lists instead of generic Lists:
public static class IEnumerableExtensions { public static PointList ToPointList(this IEnumerable list) { return new PointList(list); } }
If we find more issues, we will update the page.
It’s worth noting that the compiler is continuously improving, and many of these issues may not be issues in the future.
LINQ expressions and value types
Certain LINQ expressions also don’t work if the IEnumerable contains value types (it’s essentially the same issue as above). In this case, you may need to convert the IEnumerable to a custom IEnumerable such as the one above before making the LINQ call.