An Introduction to the Swift Compiler Driver

This article contains some content especially for patrons. Although it reads coherently as-is, to read the full article, please consider supporting me on Patreon, or click here if you are already a patron. $10/month gives you access to all content I will ever write on this website.

The first article of this series on the Swift compiler explained that the compiler is just a command-line program, named swift or swiftc. This article explains what that command-line program does.

Specifically, this article explains the Swift "driver". This is the first bit of code that is executed when users invoke swift or swiftc on the command line.

Like any other C, C++, or Objective-C command-line program, swift and swiftc begin by executing an int main() function:

swift/tools/driver/driver.cpp

111  int main(int argc_, const char **argv_) {
...
207  }

The main() function calls out to code in a Swift compiler library, libswiftDriver, in order to break up a single invocation of swift or swiftc into smaller chunks of work. These smaller chunks are called "jobs".

Swift driver jobs by example: compiling Swift source code into an executable

For example, let's say I wanted to compile the following Swift source file, hello.swift, into an executable:

hello.swift

print("Hello!")

To produce an executable named hello, I'll invoke swiftc on the command line:

swiftc hello.swift -o hello

This produces an executable named hello, by first compiling the Swift code into a .o object file, and then linking that file into an executable named hello. I can request to see the exact series of jobs that led to the final hello executable by using the -driver-print-jobs option:

swiftc -driver-print-jobs hello.swift -o hello

This outputs two jobs:

$10+ patron-only content

An explanation of the two jobs involved in compiling 'hello.swift', as a demonstration of why the Swift driver is helpful to end users.

I am a patronBecome a patron

My example above may have been too simple to demonstrate just how useful the Swift driver is – it only took two jobs to do what I wanted. However, compiling multiple Swift source files requires a separate swift -frontend job for each one. Producing a .swiftmodule from those source files requires invocations of swift -frontend -merge-modules, as well as other jobs. The Swift driver coordinates all of this work for me, translating a simple invocation such as swiftc -emit-module into what is sometimes dozens of jobs.

If that weren't enough, the Swift driver is also responsible for determining when jobs do not need to be run, because their output is already available and up-to-date. This is known as "incremental building". If you're interested in helping reduce the amount of time it takes for your Swift source code project to compile, you'll want to understand how the driver accomplishes this. It's a topic I plan on writing about at length in future articles.

$10+ patron-only content

An explanation of the difference between what I think of when I think of the Swift "compiler", and what is referred to as the Swift "driver".

I am a patronBecome a patron

A bird's-eye view of the code in the Swift driver

The basic idea of the Swift driver is that it splits up invocations of swift and swiftc into other, more granular invocations of swift -frontend, ld, and other programs. These child invocations are called "jobs". So, how does it accomplish this? What does the source code in the Swift driver look like, at a high level?

$10+ patron-only content

A breakdown of the source code that makes up what is referred to as the Swift "driver": the driver executable, the libswiftDriver interfaces, and the libswiftDriver implementation code.

I am a patronBecome a patron

How swift, swiftc, and libswiftDriver are built (and just what is the difference between swift and swiftc, anyway?)

I explained the Swift build system and how the swift executable is built in previous articles. To summarize those articles:

$10+ patron-only content

A summary of this series' posts on the Swift build system.

I am a patronBecome a patron

The swift/tools/driver/CMakeLists.txt file doesn't just define how the swift executable is built. It also defines a symlink, swiftc, that points to swift:

swift/tools/driver/CMakeLists.txt

 1  add_swift_host_tool(swift
 2    api_notes.cpp
 3    driver.cpp
 4    autolink_extract_main.cpp
 5    modulewrap_main.cpp
 6    swift_format_main.cpp
 7    LINK_LIBRARIES
 8      swiftDriver
 9      swiftFrontendTool
10    LLVM_COMPONENT_DEPENDS
11      DebugInfoCodeView
12    SWIFT_COMPONENT compiler
13  )
..
34 add_swift_tool_symlink(swiftc swift compiler)
35 add_swift_tool_symlink(swift-autolink-extract swift autolink-driver) 36 add_swift_tool_symlink(swift-format swift editor-integration)

The name of the function add_swift_tool_symlink() is fairly straightforward, and so I can guess that it creates a symlink named swiftc that points to the executable swift. To confirm this guess, I can use the techniques I described in Reading and Understanding the CMake in apple/swift. A git grep for function(add_swift_tool_symlink reveals the function's definition in swift/cmake/modules/AddSwift.cmake. The function calls through to two LLVM CMake functions. Two more git grep invocations, this time in the LLVM repository, can point me to those functions' definitions, in llvm/cmake/modules/AddLLVM.cmake.

As the CMake confirms, the swiftc executable is really just a symlink to the swift executable. But why create a symlink?

Swift "driver modes", or: "A swift by any other name"

$10+ patron-only content

An explanation of Swift "driver modes", such as 'swift' and 'swiftc'.

I am a patronBecome a patron

This is actually a fun quirk in how Swift works: you can't invoke swift through a symlink the driver code isn't aware of. For example, I tried creating a symlink to my swift executable, like so:

# Change directories to where my 'swift' executable lives:
cd ~/local/Source/apple/build/Ninja-ReleaseAssert/swift-macosx-x86_64/bin/

# Create an alias: 'my-swift-alias' points to 'swift':
ln -s swift my-swift-alias

# Attempt to print the help text for 'swift', by invoking
# my alias:
my-swift-alias -help

Sure enough, this results in an error. The Swift driver doesn't recognize the my-swift-alias driver mode:

<unknown>:0: error: invalid value 'my-swift-alias' in '--driver-mode='

$10+ patron-only content

A workaround for the invalid driver mode error.

I am a patronBecome a patron

Another look at the driver executable and libswiftDriver source code, and next steps

$10+ patron-only content

A recap of the content in this article.

I am a patronBecome a patron

Parsing command-line arguments and splitting up a driver invocation into child jobs involves a lot of different classes, most of which are defined in libswiftDriver. To name just a few, there's swift::Driver, swift::Compilation, swift::driver::ToolChain, swift::Action, swift::JobAction, swift::Job, and more. I'll go over the interactions between these classes in more detail in the next article in this series, and then I'll start writing about incremental compilation and other advanced features in libswiftDriver.

In the meantime, try exploring this treemap of the source code in the Swift driver. Each rectangle is sized proportional to the number of source lines of code it contains. Click or tap on a rectangle to view it in GitHub.

The majority of the source code we'll cover next time is in three files:

  1. swift/tools/driver/driver.cpp
  2. swift/lib/Driver/Driver.cpp
  3. swift/lib/Driver/ToolChains.cpp

If you enjoy reading descriptions of Swift compiler such as this one, please consider supporting me on Patreon, and look forward to the next article!