Automatically migrate iOS (ARM) applications to macOS (x86) using Bitcode

Original author: Steve Troughton-Smith
  • Transfer
When Apple introduced Bitcode technology and made it mandatory for watchOS and tvOS, the company seemed to shrug off questions about why it was needed at all. She only vaguely said that it helps to customize binary files and uses the latest compiler improvements.

Since then, Bitcode has played an important role in the smooth transition of watchOS to 64 bits, where developers did not even have to recompile their applications in the directory. Apple itself did this automatically: all applications started working on the Apple Watch Series 4. You probably didn’t even notice that the migration has occurred.

What is Bitcode? Well, bitcodewith small b, this is the architecture-specific intermediate representation used by LLVM, and Bitcode with large B refers to a set of functions that allow you to embed this view in your Mach-O binary, and the mechanisms by which you can give this file to the App Store .

Bitcode is not as flexible as the source code, but it is much more flexible than the embedded binary, with metadata and annotations for the compiler. In practice, you (or Apple) can easily take the Bitcode blobs from the application and recompile them into a fully functioning copy of your application. Switching from armv7 to armv7s or from arm64 to arm64e is very cool and saves developers who have to recompile the binary every time Apple changes ARM chips. Bitcode has long been used by Apple in OpenGL drivers, so the driver can be optimized on the fly for various GPU architectures.

We have seen Microsoft leverage static recompilation on the Xbox One, providing access to a whole library of games originally written for the Xbox 360 (under PowerPC), completely without developers or access to the source code. And without an intermediary like Bitcode, which simplifies the process.

Of course, the ghost of macOS on ARM has been hanging around for many years. Many wondered if this would make it easier to port applications using Bitcode. As a result, they came to a consensus that Bitcode is not suitable for transferring between radically different architectures such as Intel and ARM.

This did not convince me, so I decided to check!

To get started, we need a simple test application in Objective-Cwith Bitcode; it is usually included only when creating an archive for the App Store, so you need to force it to be included in the regular assembly. You can use a flag -fembed-bitcodeor custom build parameter:

BITCODE_GENERATION_MODE = bitcode

Create a binary for the Generic iOS Device or connected device, as usual. It seems that Bitcode is not built into arm64e assemblies (for example, if you have an A12 device), so you can turn off the Xcode setting “compile only for active architectures” and directly compile for arm64.

Using the ebcutil tool , all Bitcode objects are easily retrieved from a compiled binary.

ebcutil -a arm64 -e path/to/MyApp.app/MyApp

Then recompile each Bitcode object for Intel.

for f in *;
    do clang -arch x86_64 -c -Xclang -disable-llvm-passes -emit-llvm -x ir -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk $f -o $f.o;
done

Now we will link the compiled blobs back to the binary file.

clang -arch x86_64 -mios-version-min=12.0 -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk *.o -o path/to/MyApp.app/MyApp

If it works, now we have the x86 version of the original arm64 application! In theory, it can be placed directly in the iOS simulator window, installed and launched.

This is a very important fact: you can statically transfer binary files between Intel and ARM platforms if they include Bitcode . It really works!


Pitfalls for more complex projects


It seems that ARC uses the built-in assembler, so for the transfer from arm64 to x86 at the moment you have to disable ARC.

Some types of blocks, such as completion handlers, start the compiler with unacceptable instructions. If you get an X87 error, this is probably the problem.

Why is Objective-C? Well, Swift is designed with ARC in mind. I don’t think there is a way to avoid the aforementioned inline assembler, so the recompilation will now fail.
Let's take one more step: we ’ll use marzipanify to convert this Intel iOS application into a Mac program that works with Marzipan.



It was easy!

Theoretically, this means that Apple has a way to run any iOS application from the App Store on a Mac without requiring developers to update or recompile their applications.


What if the Mac switches from Intel to ARM chips? Well, as you can see, with Bitcode, it can transfer all Bitcode-enabled applications to the Mac App Store without the help of developers, so it will be ready to change the processor from day one. This gives Apple more freedom. Now you don’t need to announce in advance the transition to new processors a year ahead of time, and technology like Rosetta is no longer needed.

Obviously, we have not reached this point: today Apple does not include Bitcode for applications in the Mac App Store, and today Bitcode may not be ideal for such an architectural transfer. In place of Apple, I would focus on these two factors, and, of course, enable Bitcode for all Marzipan apps on macOS 10.15.

Also popular now: