If you would prefer to watch a video instead, you can check it out below!
At Embrace, we help mobile companies solve their most difficult production issues. In this series, we’ll cover interesting bugs that mobile developers might come across, and we’ll provide tips and tricks for finding and fixing them.
You can check out previous bugs here:
- Blank Web Views Caused by Downgrading
- Blank Web Views Caused by Killing the Content or Networking Process
In this post we’ll cover bitcode compilation errors in Xcode, specifically how to spot them and what you can do to prevent them from happening.
What a Bitcode Compilation Error Looks Like
There have been a lot of Xcode updates in the past few quarters, and whenever that happens, developers start to hit errors like these:
error: Failed to compile bundle: /var/folders/…/Embrace.armv7.xar
fatal error: error in backend: Unknown specifier in datalayout string Exited with 70
If you see these errors in a build log and Apple rejects your App Store submission, it’s almost certainly a bitcode problem. You might have heard about bitcode or LLVM (Low Level Virtual Machine), but you might not know what they mean or how they affect your application.
No problem. We’ll clarify that, but before we dive into that side of things, we need to talk about how code compilation on iOS used to work.
The Old Way To Compile iOS Apps - GCC Compilation
When the iPhone was first released, the compiler that built programs was called GCC, the GNU Compiler Collection. It was a legacy Linux-based compiler for C or Objective-C. The key point is that it uses a one-step compilation process. You take your code, run it through GCC, and get binary assembly (ASM) out the other side that an iPhone can run. You upload the assembly to Apple and then your users download it and run your app.
The New Way To Compile iOS Apps - LLVM
A few years ago, Apple decided to move to the LLVM (Low Level Virtual Machine) compiler. LLVM is a two-step process, unlike GCC.
In GCC, the code gets directly compiled to ASM.
In LLVM, the code first runs through a Clang Frontend that transforms it into bitcode, which is an intermediate representation of your source code. Then, a separate program called the LLVM Backend takes the bitcode and turns it into ASM.
The reason for splitting the compilation into two steps is to allow us to make improvements in how the bitcode changes to ASM without having to tokenize the C code every single time. There are a lot of performance improvements in this new model.
But why did Apple make this change, and why do they push you so hard to use bitcode in your builds?
The Problem: Fat Binaries
It really comes down to binary download size and what an app has to download. In the very early days of iPhone, we could only support what we called a “fat binary,” which means it has a slice for every CPU type that it needs to run on.
If your iPhone app was going to support 32-bit and 64-bit ARM CPUs, it would have to include two copies of your code. Your users would download both copies even though only one copy could ever run on their phone, which would waste a lot of download bandwidth.
The Solution: Sliced Binaries
Apple has since moved to a sliced binary format, which is fully supported by GCC. What this means is that you can send Apple all the binary slices that your app supports, and they will only serve the slice that each individual device needs. For example, an Armv7s device can download only 7MB instead of the entire 32MB that comprises all the app’s individual slices.
Now, this is an impressive achievement, but bitcode lets Apple do even better.
Because bitcode is a two-step process, you upload your bitcode to Apple, and Apple actually runs the LLVM Backend for you in their server. Apple then gives each version of the iPhone a custom binary for your app that is optimized specifically for that phone. This provides the best performance and the best download sizes.
But it also lets Apple do something really interesting. If they decide to fix a bug or otherwise improve the LLVM Backend, they can recompile your bitcode and serve a new version to your users without you having to upload a new version of your code to Apple.
This is a very powerful concept that allows improvements to apps without requiring the time or effort of the individual app developers.
But if this new process results in so many improvements, why do users hit bitcode compilation errors when they build their apps?
How Xcode Factors Into It
Your application is built with Xcode, which is actually quite complicated. It has a UI, but it also has a set of SDKs in it. And each SDK includes a version of LLVM inside of it. For example, iOS SDK 13.2 shipped with LLVM v8.0.0, whereas iOS SDK 13.5 shipped with LLVM v9.0.0.
So what’s the problem?
Bitcode Is Not Forwards Compatible
LLVM 8 can read bitcode from version 8 or earlier, but it cannot read bitcode from version 9. In other words, LLVM from a previous version will not be able to read bitcode that is compiled using a later version of LLVM.
One way this can become a problem is when your version of Xcode differs from the version of Xcode used to build third-party SDKs that you are integrating into your app. For example, let’s say you want to integrate Embrace into your mobile app.
The version of Embrace was built with iOS SDK 13.4 (which uses LLVM 8.0.1).
But you try to build your app with the Xcode from iOS SDK 13.2 (which uses LLVM 7).
The result will be compilation errors, and Apple will reject your build. The reason is that every module in your library has to be at the same bitcode level in order to successfully compile.
Summing It All Up
If you see weird build errors and Apple is rejecting your build, it's almost certainly going to be some bitcode incompatibility issue. The first thing you should do is to check your Xcode versions and make sure that you're using the latest version that Apple supports. That will get you the latest tools, and you can still target whatever SDK you need for your app. By using the latest version of Xcode, you ensure that the bitcode version you make will be compatible with everything that Apple wants to link with it.
This post should help you solve some difficult build problems you might run into in the future. If you enjoyed it, please send any interesting iOS bugs you run across to eric dot email@example.com. We’re always looking for bugs to share, and hopefully, next month we will have something even more interesting to show you. Thanks for reading!
Who We Are
Embrace is a mobile monitoring and developer analytics platform. We are a one-stop shop for your mobile app’s performance and error debugging needs. If you’d like to learn more about Embrace, you can check out our website or visit our docs! Feel free to ask us a question or get right down to it and see the product in action with a live demo.