|
| 1 | +# The Swift Driver, Compilation Model, and Command-Line Experience |
| 2 | + |
| 3 | +_or, "why can't I only compile the files that changed?"_ |
| 4 | + |
| 5 | +The Swift compiler's command-line interface resembles that of other compilers, |
| 6 | +particularly GCC and Clang. However, Swift's compilation model and some of its |
| 7 | +language features make it a bit tricky to plug into a larger build system. In |
| 8 | +particular, there's no correct way to specify a "one command per file" build |
| 9 | +rule for a normal Swift module. |
| 10 | + |
| 11 | +("What?" I know! Read on.) |
| 12 | + |
| 13 | +The target audience for this document is people who want to integrate the Swift |
| 14 | +compiler into their build system, rather than using Xcode or the package |
| 15 | +manager (`swift build`). If you're looking to work on the driver itself...well, |
| 16 | +this is probably still useful to you, but you should also check out |
| 17 | +DriverInternals.rst and maybe DependencyAnalysis.rst as well. If you're just |
| 18 | +using Xcode or SwiftPM and want to find out what mysterious command-line |
| 19 | +options you could be passing, `swiftc --help` is a better choice. |
| 20 | + |
| 21 | +If you're invoking `swift -frontend` directly, and you aren't working on the |
| 22 | +compiler itself...well, this document should convince you to not do that. |
| 23 | + |
| 24 | +Some terms: |
| 25 | + |
| 26 | +- For the purposes of this document, a _module_ is a single distributable unit |
| 27 | + of API. (We use this term for a lot of other things too, though; check out |
| 28 | + Lexicon.rst for the full list.) "Foundation" is a single module, as is the |
| 29 | + Swift standard library ("Swift"). An app is a module too. |
| 30 | + |
| 31 | +- A _compilation unit_ is a set of source files that are compiled together. In |
| 32 | + Swift, everything intended to be in the same module must be part of the same |
| 33 | + compilation unit, and every file in the same compilation unit are assumed to |
| 34 | + be part of the same module. Doing anything else is unsupported. |
| 35 | + |
| 36 | +- The _driver_ is the program that's run when you invoke `swift` or `swiftc`. |
| 37 | + This doesn't actually compile anything itself; instead it invokes other tools |
| 38 | + to produce the desired output. |
| 39 | + |
| 40 | +- The _frontend_ is the program that actually compiles code (and in interpreter |
| 41 | + mode, executes it via a JIT). It's hidden behind `swift -frontend`. |
| 42 | + |
| 43 | + The frontend is an implementation detail. No aspects of its command-line |
| 44 | + interface are considered stable. Even its existence isn't guaranteed. |
| 45 | + |
| 46 | +- The _REPL_ is a mode of the debugger (LLDB) that is launched by the driver |
| 47 | + when you invoke `swift` with no inputs. It doesn't actually come up again in |
| 48 | + this document, but it's probably worth mentioning for completeness. |
| 49 | + |
| 50 | + |
| 51 | +## What gets run? ## |
| 52 | + |
| 53 | +Part of Swift's model is that a file can implicitly see entities declared |
| 54 | +elsewhere in the same module as long as they have not been marked private. |
| 55 | +Consequently, compiling any one file needs some knowledge of all the others. |
| 56 | +However, we don't want a build model that rules out incremental builds, nor one |
| 57 | +that forces non-parallel compilation. Consequently, the Swift driver has three |
| 58 | +stages of subprocesses: |
| 59 | + |
| 60 | +1. "Frontend jobs" |
| 61 | +2. "Module merging" |
| 62 | +3. Linking |
| 63 | + |
| 64 | +Dependencies between the subprocesses are managed by the driver. Outputs are |
| 65 | +controlled by `-o` and other various compiler flags; see `swiftc --help` for |
| 66 | +more information. |
| 67 | + |
| 68 | + |
| 69 | +### Frontend jobs ### |
| 70 | + |
| 71 | +A normal Swift compilation starts off with as many _frontend jobs_ as input |
| 72 | +files. Each invocation of the Swift frontend parses every file in the module, |
| 73 | +but also has a particular file marked as the _primary file._ A job is only |
| 74 | +responsible for compiling its primary file, and only does as much work as it |
| 75 | +needs to to compile that file, lazily type-checking declarations in other files |
| 76 | +in the module. |
| 77 | + |
| 78 | +A frontend job emits diagnostics, an object file, dependency information (see |
| 79 | +"Incremental Builds" below), and a _partial module file._ |
| 80 | + |
| 81 | + |
| 82 | +### Module merging ### |
| 83 | + |
| 84 | +Swift doesn't have header files; instead it uses a generated binary format |
| 85 | +called a _Swift module file_ (.swiftmodule). With N frontend jobs, it becomes |
| 86 | +necessary to stitch all the partial module files together. This job is also |
| 87 | +performed by the frontend in a step called "module merging". Module merging |
| 88 | +only produces a single merged module file (and accompanying documentation file |
| 89 | +with the extension .swiftdoc), which can then be imported in later builds. |
| 90 | + |
| 91 | +The Swift module file format is not stable; using it across compiler versions |
| 92 | +is not supported. It also currently includes a ton of private information about |
| 93 | +both your module and your compilation environment, so make sure you don't |
| 94 | +distribute them with your final build products. |
| 95 | + |
| 96 | + |
| 97 | +### Linking ### |
| 98 | + |
| 99 | +The last stage of compilation is linking. In addition to actually invoking the |
| 100 | +linker, the compiler needs to accomplish two other tasks: |
| 101 | + |
| 102 | +- **Autolinking:** Swift object files encode information about what libraries |
| 103 | + they depend on. On Apple platforms the linker can read this information |
| 104 | + directly; on other platforms it's extracted using the |
| 105 | + `swift-autolink-extract` helper tool. Of course the build system can also |
| 106 | + provide manual link commands too. |
| 107 | + |
| 108 | +- **Debugging:** In addition to the usual DWARF format, interactive debugging |
| 109 | + of a Swift program requires access to its module. This is supported via a |
| 110 | + custom linker flag on Apple platforms and by the `swift-modulewrap` tool |
| 111 | + elsewhere. Apple platforms also invoke `dsymutil` after linking to produce a |
| 112 | + dSYM debugging archive, so that the program is still debuggable even once the |
| 113 | + object files have been cleaned. |
| 114 | + |
| 115 | + |
| 116 | +## Output File Maps ## |
| 117 | + |
| 118 | +> There are three numbers in computer science: 0, 1, and N. |
| 119 | +
|
| 120 | +The problem with running a separate frontend job for each input file is that it |
| 121 | +suddenly becomes much more complicated to determine where outputs go. If you're |
| 122 | +just going to link everything together at the end this isn't really a problem, |
| 123 | +but a lot of build systems just want the compiler to produce object files. If |
| 124 | +you want to use Swift's cross-file dependency tracking, or get Clang-style |
| 125 | +serialized diagnostics (.dia files) instead of just text output, those are also |
| 126 | +generated per-input-file. And some build systems (\*cough\*Xcode\*cough\*) want |
| 127 | +to individually track persisted output files, instead of dumping them into a |
| 128 | +temporary directory. Trying to specify the outputs for every input file on the |
| 129 | +command line would be awkward and ridiculous, so instead we use an _output file |
| 130 | +map._ |
| 131 | + |
| 132 | +An output file map contains a JSON dictionary that looks like this: |
| 133 | + |
| 134 | +```json |
| 135 | +{ |
| 136 | + "/path/to/src/foo.swift": { |
| 137 | + "object": "/path/to/build/foo.o", |
| 138 | + "dependencies": "/path/to/build/foo.d", |
| 139 | + "swift-dependencies": "/path/to/build/foo.swiftdeps", |
| 140 | + "diagnostics": "/path/to/build/foo.dia" |
| 141 | + }, |
| 142 | + "/path/to/src/bar.swift": { |
| 143 | + "object": "/path/to/build/bar.o", |
| 144 | + "dependencies": "/path/to/build/bar.d", |
| 145 | + "swift-dependencies": "/path/to/build/bar.swiftdeps", |
| 146 | + "diagnostics": "/path/to/build/bar.dia" |
| 147 | + }, |
| 148 | + "": { |
| 149 | + "swift-dependencies": "/path/to/build/main-build-record.swiftdeps" |
| 150 | + } |
| 151 | +} |
| 152 | +``` |
| 153 | + |
| 154 | +The build system is responsible for generating this file. The input file names |
| 155 | +don't have to be absolute paths, but they do have to match the form used in the |
| 156 | +invocation of the compiler. No canonicalization is performed. The special `""` |
| 157 | +entry is used for outputs that apply to the entire build. |
| 158 | + |
| 159 | +The "dependencies" entries refer to Make-style dependency files (similar to GCC |
| 160 | +and Clang's `-MD` option), which can be used to track cross-module and header |
| 161 | +file dependencies. "swift-dependencies" entries are required to perform |
| 162 | +incremental builds (see below). The "diagnostics" entries are only for your own |
| 163 | +use; if you don't need Clang-style serialized diagnostics they can be omitted. |
| 164 | + |
| 165 | +The output file map accepts other entries, but they should not be considered |
| 166 | +stable. Please stick to what's shown here. |
| 167 | + |
| 168 | +(Note: In the example output file map above, all of the per-file outputs are |
| 169 | +being emitted to the same directory. [SR-327][] covers adding a flag that would |
| 170 | +infer this behavior given a directory path.) |
| 171 | + |
| 172 | + [SR-327]: https://bugs.swift.org/browse/SR-327 |
| 173 | + |
| 174 | + |
| 175 | +## Whole-Module Optimization ## |
| 176 | + |
| 177 | +When the `-whole-module-optimization` flag is passed to Swift, the compilation |
| 178 | +model changes significantly. In this mode, the driver only invokes the frontend |
| 179 | +once, and there is no primary file. Instead, every file is parsed and |
| 180 | +type-checked, and then all generated code is optimized together. |
| 181 | + |
| 182 | +Whole-module builds actually have two modes: threaded and non-threaded. The |
| 183 | +default is non-threaded, which produces a single object file for the entire |
| 184 | +compilation unit (.o). If you're just producing object files, you can use the |
| 185 | +usual `-o` option with `-c` to control where the single output goes. |
| 186 | + |
| 187 | +In threaded mode, one object file is produced for every input source file, and |
| 188 | +the "backend" processing of the generated code (basically, everything that's |
| 189 | +not Swift-language-specific) is performed on multiple threads. Like |
| 190 | +non-whole-module compilation, the locations of the object files is controlled |
| 191 | +by the output file map. Only one file is produced for each non-object output, |
| 192 | +however. The location of these additional outputs is controlled by command-line |
| 193 | +options, except for Make-style dependencies and serialized diagnostics, which |
| 194 | +use an entry under `""` in the output file map. |
| 195 | + |
| 196 | +Threaded mode is controlled by the `-num-threads` command-line option rather |
| 197 | +than `-j` used to control the number of jobs (simultaneous subprocesses spawned |
| 198 | +by the driver). Why? While changing the number of jobs should never affect the |
| 199 | +final product, using threaded vs. non-threaded compilation does. |
| 200 | + |
| 201 | +Specifics of whole-module optimization mode are subject to change, particularly |
| 202 | +in becoming more like non-whole-module builds. |
| 203 | + |
| 204 | + |
| 205 | +## Incremental Builds ## |
| 206 | + |
| 207 | +Incremental builds in Swift work by primarily by cross-file dependency |
| 208 | +analysis, described in DependencyAnalysis.rst. Compiling a single file might be |
| 209 | +necessary because that file has changed, but it could also be because that file |
| 210 | +depends on something else that might have changed. From a build system |
| 211 | +perspective, the files in a particular module can't be extracted from each |
| 212 | +other; a top-level invocation of the compiler will result in a valid |
| 213 | +compilation of the entire module, but manually recompiling certain files is not |
| 214 | +guaranteed to do anything sensible. |
| 215 | + |
| 216 | +Performing an incremental build is easy; just pass `-incremental` and be sure to |
| 217 | +put "swift-dependencies" entries in your output file map. |
| 218 | + |
| 219 | +Incremental builds don't mix with whole-module builds, which get their |
| 220 | +whole-module-ness by rebuilding everything every time *just in case* there's |
| 221 | +some extra optimizations that can be made by repeated inlining, or by *really* |
| 222 | +being sure that a method is never overridden. |
| 223 | + |
| 224 | +(Can this sort of information be cached? Of course. But there's a question of |
| 225 | +whether it's going to be easier to make whole-module builds be more incremental |
| 226 | +or to make incremental builds support more cross-file optimization.) |
| 227 | + |
| 228 | +A particular nasty bit about incremental builds: because in general the |
| 229 | +compiler only knows that a file *has* changed and not *what* changed, it's |
| 230 | +possible that the driver will start off assuming that some file won't need to |
| 231 | +be recompiled, and then discover later on that it should be. This means that |
| 232 | +it's not possible to use classic build models like Makefiles to make builds |
| 233 | +incremental, because Makefiles don't accomodate dependency graph changes during |
| 234 | +the build. |
| 235 | + |
| 236 | +Only the "frontend" tasks are considered skippable; module-merging and linking |
| 237 | +always occurs. (It is assumed that if *none* of the inputs changed, the |
| 238 | +build system wouldn't have invoked the compiler at all.) |
| 239 | + |
| 240 | +Currently Swift does not provide any way to report tasks to be compiled out to |
| 241 | +some other build system. This is definitely something we're interested in, |
| 242 | +though---it would enable better integration between the compiler and llbuild |
| 243 | +(the build system used by the package manager), which would lead to faster |
| 244 | +compile times when many dependencies were involved. |
| 245 | + |
| 246 | +The format of the "swiftdeps" files used to track cross-file dependencies |
| 247 | +should be considered fragile and opaque; it is not guaranteed to remain stable |
| 248 | +across compiler versions. |
| 249 | + |
| 250 | + |
| 251 | +## `-embed-bitcode` ## |
| 252 | + |
| 253 | +Apple's "Embed Bitcode" feature adds an extra bit of complexity: to be sure |
| 254 | +builds from the embedded bitcode are identical to those built locally, the |
| 255 | +driver actually splits compilation in two. The first half of compilation |
| 256 | +produces LLVM IR, then the driver runs so-called _backend jobs,_ which compile |
| 257 | +the IR the rest of the way into binaries. |
| 258 | + |
| 259 | +There's not really any secrets here, since it's all there in the source, but |
| 260 | +the particular mechanism isn't guaranteed by any means---Apple could decide to |
| 261 | +change it whenever they want. It's probably best not to special-case any logic |
| 262 | +here; just using the driver as intended should work fine. |
| 263 | + |
| 264 | + |
| 265 | +## So, how should I work Swift into my build system? ## |
| 266 | + |
| 267 | +Step 0 is to see if you can use the Swift package manager instead. If so, it's |
| 268 | +mostly just `swift build`. But if you're reading this document you're probably |
| 269 | +past that, so: |
| 270 | + |
| 271 | +1. Generate an output file map that contains all the per-file outputs you care |
| 272 | + about. Most likely this is just the object files and incremental build |
| 273 | + dependency files; everything else is an intermediate. (There should probably |
| 274 | + be a tool that does this, perhaps built on what the package manager does.) |
| 275 | + |
| 276 | +2. Set TMPDIR to somewhere you don't mind uninteresting intermediate files |
| 277 | + going. |
| 278 | + |
| 279 | +3. Do one of the following: |
| 280 | + |
| 281 | + - Invoke `swiftc -emit-executable` or `swiftc -emit-library`. Pass all the |
| 282 | + Swift files, even if you're building incrementally (`-incremental`). Pass |
| 283 | + `-j <N>` and/or `-num-threads <N>` if you have cores to spare. Pass |
| 284 | + `-emit-module-path <path>` if you're building a library that you need to |
| 285 | + import later. |
| 286 | + |
| 287 | + If you want debugging that's more than `-gline-tables-only`, this is the |
| 288 | + only supported way to do it today. ([SR-2637][] and [SR-2660][] are aimed |
| 289 | + at improving on this.) On the plus side, this mode doesn't strictly need |
| 290 | + an output file map if you give up incremental builds. |
| 291 | + |
| 292 | + - Invoke `swiftc -c`, then pass the resulting object files to your linker. |
| 293 | + All the same options from above apply, but you'll have to manually deal |
| 294 | + with the work the compiler would have done automatically for you. |
| 295 | + |
| 296 | + - Invoke `swiftc -c` with `-###`. Then run all of the outputted commands |
| 297 | + that include `-primary-file`, then run the remaining commands in order |
| 298 | + (they may have dependencies). If none of the commands have `-primary-file` |
| 299 | + in them, they're not parallelizable, sorry. |
| 300 | + |
| 301 | + This is the most hacky approach, because (a) it involves looking for an |
| 302 | + internal flag in a non-stable interface, and (b) you don't get anything |
| 303 | + incremental out of this. We could stand some improvements here. |
| 304 | + |
| 305 | + Whatever you do, do *not* invoke the frontend directly. |
| 306 | + |
| 307 | + |
| 308 | +### Questions ### |
| 309 | + |
| 310 | +_Can I link all the object files together in the same binary, even if they came |
| 311 | +from multiple modules?_ |
| 312 | + |
| 313 | +This is not currently supported, and debugging probably won't work. (See |
| 314 | +[SR-2637][] and [SR-2660][] for more details.) However, if you are using |
| 315 | +`-gnone` or `-gline-tables-only`, the worst you will suffer is more symbols |
| 316 | +being visible than are strictly necessary. |
| 317 | + |
| 318 | + [SR-2637]: https://bugs.swift.org/browse/SR-2637 |
| 319 | + [SR-2660]: https://bugs.swift.org/browse/SR-2660 |
0 commit comments