Ocean Village - A dev blog

Ouch - F# + il2cpp

December 11, 2019 - Wednesday afternoon

The hint was in the thread. Script debugging. I tried turning it off in build settings but that didn't fix it. What I was missing was that the linker was breaking on the debug codepath for the F# dll. Deleting the .pdb built with it worked to get around the linker issue.

But, that just got me to the entrance of hell.

I have a GitLab issue titled il2cpp nightmare to track getting il2cpp working. I'm getting to the light at the end of the tunnel here, I think. To be fair, some of these issues are not just with il2cpp but ahead-of-time compilation with F# in general. These problems exist in some form in both .net Native and CoreRT. But my horror is only with il2cpp, since it's been forced upon me by Google's 64-bit requirement and Unity's lack of a 64-bit Mono option.


Reflection.Emit doesn't work at all.

FsPickler sort of uses this but that can be disabled. Building it with EMIT_IL not set is still is not working, and serialization is yet again an open issue. At least since it's open source, I have the option of trying to remove whatever broken functionality I don't need and rebuilding.

I honestly don't know if I can get FsPickler to work on il2cpp, or if I'd trust it continues to work with all the other il2cpp issues below. Apparently someone was able to get it working with AOT, though not sure which compiler. There's an alternative with F# bindings I'll probably have to check out - ZeroFormatter. It advertises both F# and il2cpp support. It also hasn't been updated in 3 years. Shoot me.

Deeply nested generics don't work.

In OO code, you shouldn't see this, but it's easy to get to deeper nesting with functional patterns. Il2cpp decided on an arbitrary limit of 7 nested generics and it appears that it's total nests, not deepest nest (so a 2-tuple gets you two levels already. Official comment says this is not the case, but ILSpy + a crash log say otherwise) at which point you get a runtime(!!) error.

Avoiding runtime errors is one of my major reasons for choosing F#, so this really, really bummed me out. Knowing that a refactor could accidentally bump me over the 7 limit and I won't know unless I happen to test that codepath running the game is a real let down.

Type.MakeGenericType and MethodInfo.MakeGenericMethod don't work.

I don't use these, but FSharp Core does for object printing. This means no ToString works by default for Discriminated Unions or Records. These are, of course, runtime errors. Every record or DU I want to stringify, I'll need to override. There was a workaround where you can specify a text file with the necessary types, but apparently support for that workaround was dropped. ObjectPrinter is in an internal FSharp.Core module PrintfImpl, which means the only available workaround (fake a concrete type in an unused class) doesn't work.

You can provide a link.xml file to avoid stripping certain assemblies, but this also breaks for various reasons. I also have no idea if this actually works, as the official directions to compare to stripped/unstripped Unity-built assemblies don't actually work - the files aren't anywhere to be found.


One of the worst aspects of trying to get il2cpp working has been how dispersed each clue to progress through each error was. Trying to find solutions had me traveling throughout time and space - threads from years ago, between Unity forums, stackoverflow, their bug tracker, blog posts. The Unity documentation makes it sound as if anything that works with Mono backend works with il2cpp.

As you run into issues, the limitations are only mentioned in forum posts or you find out on stack overflow. You may even find an official workaround on the Unity forums. Then, after some time struggling with it, you may then find in a message halfway down some thread that the official workaround is no longer supported. You may find that things broken will remain broken only in bug tracker items marked as Won't Fix or By Design.

Every attempt at fixing an il2cpp bug requires a full build and to play the game reaching that codepath. The build takes several minutes by itself. Please! Unity! Make these compile errors!

As the now only viable backend for mobile platforms on Unity, il2cpp has horrendous documentation. I just want a table of known issue to current workaround (or N/A) - not to spend dozens of hours digging this stuff out from the bowels of the internet.

To top it off, I hoped that at this trade-off of runtime safety (and frankly, maintenance nightmare), I'd at least get a performance boost from native binaries. I've yet to see any, and in fact it appears the performance is worse, an experience which a quick Google search shows many have had. A performance benchmark has it from around 2x faster (for one test case) to between 3x - 64x(!) slower than Mono. There's room to tweak this - but I'm now below my target of 60 fps at 500 residents.

Ok, end of rant.


This has put a bit of a damper on my spirit, but in the end I can now get a build published to internal test release track on the Play Store. In any case, this would've been necessary for iOS. C'est la vie.

P.S. On a positive note, I do appreciate that without il2cpp we don't get cross-platform Unity, and from what I found the actual support replies from Unity have been very patient and respectful - but since cross-platform is heavily advertised I would expect better documentation.

I did investigate Godot more, but it looks like they don't have basic Android mono support yet. Graphics programming is interesting enough that I wouldn't mind experimenting directly with Vulkan, which has .net bindings, but attempting this now would absolutely derail game development so I'll just #dealwithit.


Say hi! Enable JS to see my email (or send to this domain)