Skip to content

Commit 33228c6

Browse files
authored
[docs] Add an external description of the swiftc driver. (#4992)
Aimed at people writing build systems who won't be satisfied with "just use xcodebuild" or "just use SwiftPM", mostly because they'll go ahead doing what they're doing anyway and it'll be incorrect.
1 parent 1817b6a commit 33228c6

File tree

1 file changed

+319
-0
lines changed

1 file changed

+319
-0
lines changed

docs/Driver.md

Lines changed: 319 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,319 @@
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

Comments
 (0)