What are the stages from Swift source to machine code, and why does Swift have SIL?
Predict first
Does this get past SILGen?
func f() {
let x: Int
print(x)
}
- Yes
- No
Predict first
Does it compile when you run the SIL passes (-emit-sil)?
func f() {
let x: Int
print(x)
}
- Yes
- No
Predict first
Is this a syntax error?
let x: Int = "a"
- Yes
- No
Mental model. It is a factory line: parse the plan, check it makes sense, translate it into Swift-specific drawings (SIL), improve those, and only then hand a generic blueprint to the machine shop (LLVM).
The compiler works in stages. Parsing turns text into a syntax tree. Semantic analysis (Sema) resolves names and type-checks, using a constraint solver for expressions. SILGen lowers the typed tree to raw SIL, then a run of mandatory SIL passes performs analyses and diagnostics that must happen in every build. With -O the SIL optimiser runs: inlining, generic specialisation, devirtualisation and ARC optimisation. IRGen then emits LLVM IR, LLVM optimises it and generates machine code, and the linker produces the binary.
SIL exists because LLVM IR is too low-level. By then the compiler no longer knows what a class, a protocol witness, a generic parameter or an ownership rule is, so optimisations such as “this class has no subclasses, call it directly” or “specialise this generic for Int” are only possible earlier. It also lets the compiler report errors from the language’s own rules, such as using a variable before it is initialised, at a stage that still understands the source.
SILGen only lowers the code to raw SIL. The use-before-initialisation error is reported afterwards, by a mandatory SIL pass.
The definite-initialisation pass finds that x is read before it is set and reports “constant used before being initialized”.
The text parses perfectly well. The mismatch is found by type checking, a later stage.
Common traps
- “Type errors and initialisation errors come from the same place.” Type errors come from Sema; use-before-init is a SIL diagnostic, which later sections show.
- “Debug builds skip SIL.” They skip the optimiser, not the mandatory passes, so diagnostics are identical.
They might follow up with
- What is the driver?
swiftcsplits work into frontend jobs, schedules them (in parallel, per file or per module), and runs the linker. - Where does generic specialisation happen? In the SIL optimiser, which is why it is absent from
-Ononebuilds.
Related questions
- 5.1.2 What does the compiler guarantee through its mandatory SIL passes?
- 5.1.3 How do you look at what the compiler produced at each stage?
- 1.4.7 What is generic specialization? When does it not happen?
What does the compiler guarantee through its mandatory SIL passes? →