Porting the Swift Runtime to Android, Part One: The How
Shortly after Swift was open-sourced, Zhuowei Zhang sent an email to the swift-dev mailing list, introducing his fork of Swift, which could produce code that ran on Android devices. I worked on his fork, to adapt the code so that it could be merged into the Apple Swift repository. In February 2016 I sent a pull request, entitled simply “Port to Android”. It was merged in April.
What does it mean to “port” the Swift runtime to Android?
The Swift compiler is a program that simply translates Swift source code into an intermediate representation, called LLVM IR. LLVM is a program that translates LLVM IR into assembly code. Assembly code is a slightly more readable version of the 0’s and 1’s that are used to represent machine instructions. LLVM can generate assembly for many different platforms, including Android, PS4, and Windows.
Swift already generates LLVM IR, and LLVM can already turn IR into assembly code for Android. The pull request I sent to “port” the Swift runtime to Android merely added a few command line options, as well as some glue code to get it to build. Here’s what it involved:
- Modifications to the compiler itself
- Adding an Android
-targetoption to libDriver
- Adding an Android toolchain option to libDriver
- Adding an
os(Android)platform condition to libBasic
- Adding an Android
- Modifications to the Swift runtime
#if defined(__ANDROID__)to conditionally exclude unsupported features, such as stack traces
- Modifications to the Swift standard library
- Modifying the build system to link against a libicu built for Android armv7
- Modifying the build system to use Android’s libc implementation
os(Linux)checks to also include
In a future post, I’ll write about the current state of using Swift to write Android apps. In the meantime, you can read on for more technical detail on the above changes.
1. Modifications to the compiler itself
I needed to make a few changes to the compiler, the program that runs when you invoke
swift on the command line, in order for it to properly inform LLVM that
swift -target armv7-none-linux-androideabi should produce 0’s and 1’s that work on Android.
1.1. Adding an Android
-target option to libDriver
The Swift compiler is an ordinary C++ program. Like any C or C++ program, it has an
int main() function. That function almost immediately calls code in
lib/Driver library does two things:
- Parses command line arguments.
- Generates commands to shell out and execute. The commands usually just call
swift -frontendonce for each source file, then call the linker to link them together.
One of the arguments
lib/Driver parses is
-target, an option you can pass to the compiler so that it generates an executable that can be run on a specific machine. For example: to compile Swift source code for macOS 10.10, use
swift -target x86_64-apple-macosx10.10. To compile Swift source code for iOS, use
swift -target arm64-apple-ios7.0.
A few spots in the Swift codebase validated the
-target parameter being passed in, raising an error if it wasn’t recognized. My pull request added the
swift -target armv7-none-linux-androideabi option to those spots.
1.2. Adding an Android “toolchain” option to libDriver
lib/Driver parses the
-target option and chooses a “toolchain” based on that option.
As I mentioned above,
lib/Driver  parses command line arguments, and  generates command line invocations for
swift -frontend. In Swift driver parlance, a “toolchain” is an object that knows how to generate those invocations for a specific platform.
driver::toolchains::Darwin knows which arguments to pass to
swift -frontend to compile Swift source code for macOS and iOS.
driver::toolchains::GenericUnix knows how to do so for Unix platforms like Linux.
My pull request added an Android toolchain, which passes the correct arguments to
swift -frontend and the linker for Android. Most importantly, it specified
-target armv7-none-linux-androideabi when invoking the linker.
1.3. Adding an
os(Android) platform condition to libBasic
The changes above are all that’s necessary for the Swift compiler to specify that Swift code that’s been lowered to LLVM IR be translated to machine code for Android.
However, people writing Swift will probably want to write things differently when targeting Android, so my pull request added an
os(Android) platform conditional. This allows you to write the following Swift code:
// Foo.swift #if os(Android) import Glibc #else import Darwin #endif
2. Modifications to the Swift runtime
As a Swift program is running, it calls through to underlying system libraries. Not all of these libraries work the same on Android, so I needed to modify how the Swift runtime used them.
#if defined(__ANDROID__) to conditionally exclude unsupported features, such as stack traces
When you write Swift code that calls
fatalError(), or when Swift code crashes as it’s running, the Swift runtime prints a stack trace. To do so, it uses
execinfo.h, a library that exists on Linux and macOS, but not on Android. My pull request turned off stack traces on Android.
3. Modifications to the Swift standard library
The changes above allow for Swift code to be compiled for Android, and run on an Android device. However, useful Swift code uses functions like
String. These are defined in the Swift standard library.
3.1. Modifying the build system to link against a libicu built for Android armv7
String supports unicode. On macOS, Swift uses CoreFoundation to hash and compare strings. On operating systems that don’t ship with CoreFoundation, the Swift standard library uses libicu.
Swift’s build system assumed that, if you were compiling the Swift standard library in a Linux environment, you’d build the standard library for that machine, and using the libicu that environment provided. None of these assumptions were true when building the Swift standard library for Android, so my pull request modified the build system so that you could specify which libicu to use.
3.2. Modifying the build system to use Android’s libc implementation
When you write C, you have access to the C standard library. This includes functions like
ceil. Most implementations of the C standard library are the same, but some have slight differences.
In addition, the Swift build system assumed that the C standard library could be located at the system root of the environment compiling the Swift standard library, in
My pull request conditionally included libc headers on Android, as well as specified where to find libc headers for Android.
os(Linux) checks to also include
Everyone’s favorite part of writing cross-platform Swift code has got to be including the following five lines in every single file:
#if os(OSX) || os(iOS) || os(watchOS) || os(tvOS) import Darwin #elseif os(Linux) || os(FreeBSD) || os(PS4) || os(Android) import Glibc #endif
My pull request added
os(Android) to a bunch of these checks.