Port your Mac app to Apple silicon
Description: Your porting questions, answered: Learn how to recompile your macOS app for Apple silicon Macs and build universal apps that launch faster, have better performance, and support the future of the platform. We’ll show you how Xcode makes it simple to build a universal macOS binary and go through running, debugging, and testing your app. Learn what changes to low-level code you might need to make, find out how to handle in-process and out-of-process plug-ins, and discover some useful tips for working with universal apps. We’ve designed this session for experienced macOS developers who want to get their existing apps running natively on Apple silicon Macs. You can learn more about doing so in the Apple silicon documentation. For more information on the transition to Apple silicon, watch "Explore the new system architecture of Apple silicon Macs", "Bring your Metal app to Apple silicon Macs", and "Optimize Metal Performance for Apple silicon Macs". And to learn how to run your iPhone and iPad apps on Mac, check out "iPad and iPhone apps on Apple silicon Macs".
For an in-depth overview of everything Apple Silicon, check the official Apple Silicon documentation.
What the transition to Apple Silicon means
|Apple Silicon Mac||Intel-based Mac|
|Native architecture||arm 64||x86_64|
|Supported architectures||arm64 (native), x86_64 (translated via Rosetta)||x86_64|
In macOS 11, when running on Apple silicon, the CPU tab of the Activity Monitor.app has a new
Kind column showing which architecture each process runs on ("
Apple" means it's running natively in Apple Silicon,
Intel when the process is being translated via Rosetta):
Universal Mach-O binaries
Your apps and most executable code in macOS, are stored in a file format called Mach-O.
These files can either be targeting a single CPU architecture or they can be universal (a.k.a. support multiple CPU architectures).
To examine a file on disk, you can use the
- the entire process is either native or translated (cannot mix).
- Kernel extensions, AVX vector instructions, and virtualization are not supported.
Building Universal Binaries
- endianness of arm64 is the same as x86
- if your (macOS) app shares code with an iOS app, that code is guarantee to work in Apple Silicon
- Xcode 12 supports building Universal Binaries on all mac architectures (both on Intel and Apple Silicon macs)
Determining CPU page size
- Native page size on Intel is
4 kB, on Apple Silicon it's
16 kB: therefore the
PAGE_SIZEmacro is no longer a constant. Use:
PAGE_MAX_SIZEfor a compile-time upper bound
vm_page_sizeto read the actual value at runtime
- Rosetta provides 4 KB pages when translating Intel processes
#if for platforms and CPU architectures
- iOS device:
#if os(iOS) && !targetEnvironment(simulator)
When building for Apple Silicon you might encounter linker warnings (that later translate in some cryptic errors) similar to:
ignoring file when building for macOS-arm64, but attempting to link with file built for macOS-x86_64..
This happens when we have a binary dependency that hasn't been built as universal yet, the only way to fix this is to:
- remove such dependency (even just temporarily)
- wait for the dependency to provide an universal binary
What you can do now: search for precompiled binaries in your project (
.xcframework) and reach out their vendors if the binary is not universal (use
lipo --info path/to/binary to inspect them)
Building Universal Binaries steps
- Build your app as an Intel app in Xcode 12 first (make sure it builds fine with no warnings)
- Build your app natively for Apple Silicon
- fix non-portable code issues (see #if chapter above)
- fix link-time issues (see
Precompiled binarieschapter above)
Running, Testing, Debugging
- When running tests, they will run only in the selected architecture
- The default architecture is the one the mac machine is running on
- Always run your tests on both architectures
Asymmetric CPU cores on Apple Silicon
- Apple Silicon Macs have two types of cores:
- High-performance (P Cores)
- Energy-efficient (E Cores)
- All can be active at the same time for highly-parallel workloads
In Instruments you can see which core is which by the associated label:
- Plug-ins are a way to dynamically load and execute code.
- Both native and translated plug-ins are supported.
- If your app is a plug-in host, and if it's using a custom plug-in loading mechanism, you will need to consider how plug-ins work on Apple Silicon Macs.
If your app supports plug-ins, it will typically discover them at runtime and then load them when needed.
Alternatively, the plug-ins can be spawned as new processes, and we call those out-of-process plug-ins. The app and the plug-in process then use some interprocess communication mechanism like XPC. Loading another plug-in typically spawns another process.
|1st party (build from source code shipped inside your app)||3rd party (precompiled binaries, shipped inside the app or separately)|
|Out-of-process||Supported||Supported, even when the binary is intel only (Rosetta will take care of that process)|
|In-process||Supported only if the running architecture matches the one in the precompiled binary)||not supported|