Skip to content

Commit 68aa44b

Browse files
committed
Add a document that describes the design of the Swift Optimizer.
1 parent f63492f commit 68aa44b

File tree

1 file changed

+211
-0
lines changed

1 file changed

+211
-0
lines changed

docs/Optimizer.rst

Lines changed: 211 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,211 @@
1+
:orphan:
2+
3+
Design of the Swift optimizer
4+
=============================
5+
6+
This document describes the design of the Swift Optimizer. It is intended for
7+
developers who wish to debug, improve or simply understand what the Swift
8+
optimizer does. Basic familiarity with the Swift programming language and
9+
knowledge of compiler optimizations is required.
10+
11+
12+
Optimization pipeline overview
13+
===============================
14+
15+
The Swift compiler translates textual swift programs into LLVM-IR and uses
16+
multiple representations in between. The Swift frontend is responsible for
17+
translating textual swift program into verified, well-formed and typed programs
18+
that are encoded in the SIL intermediate representation. The frontend emits SIL
19+
in a phase that's called SILGen (stands for SIL-generation). Next, the swift
20+
compiler performs a sequence of transformations, such as inlining and constant
21+
propagation that allow the swift compiler to emit diagnostic messages (such as
22+
warnings for uninitialized variables or arithmetic overflow). Next, the Swift
23+
optimizer transforms the code to make it faster. The optimizer removes redundant
24+
reference counting operations, devirtualizes function calls, and specializes
25+
generics and closures. This phase of the compiler is the focus of this
26+
document. Finally, the SIL intermediate representation is passed on to the IRGen
27+
phase (stands for intermediate representation generationphase) that lowers SIL
28+
into LLVM IR. The LLVM backend optimizes and emits binary code for the compiled
29+
program.
30+
31+
Please refer to the document “Swift Intermediate Language (SIL)” for more
32+
details about the SIL IR.
33+
34+
The compiler optimizer is responsible for optimizing the program using the
35+
high-level information that's available at the SIL level. When SIL is lowered to
36+
LLVM-IR the high-level information of the program is lost. It is important to
37+
understand that after IRGen, the LLVM optimizer can't perform high-level
38+
optimizations such as inlining of closures or CSE-ing of virtual method lookups.
39+
When a SIL virtual call or some code that deals with generics is lowered to
40+
LLVM-IR, to the optimizer this code looks like a bunch of loads, stores and
41+
opaque calls, and the LLVM-optimizer can't do anything about it.
42+
43+
The goal of the Swift optimizer is not to reimplement optimizations that
44+
already exist in the LLVM optimizer. The goal is to implement optimizations that
45+
can't be implemented in LLVM-IR because the high-level semantic information is
46+
lost. The swift optimizer does have some optimizations that are similar to LLVM
47+
optimizations, like SSA-construction and function inlining. These optimizations
48+
are implemented at the SIL-level because they are required for exposing other
49+
higher-level optimizations. For example, the ARC optimizer and devirtualizer
50+
need SSA representation to analyze the program, and dead-code-elimination is a
51+
prerequisite to the array optimizations.
52+
53+
The Swift Pass Manager
54+
======================
55+
The Swift pass manager is the unit that executes optimization
56+
passes on the functions in the swift module. Unlike the LLVM optimizer, the
57+
Swift pass manager does not schedule analysis or optimization passes. The pass
58+
manager simply runs optimization passes on the functions in the module.
59+
The order of the optimizations is statically defined in the file "Passes.cpp".
60+
61+
TODO: The design that's described in the paragraph below is still under
62+
development.
63+
64+
The pass manager scans the module and creates a list of functions that are
65+
organized at a bottom-up order. This means that the optimizer first optimizes
66+
callees, and later optimizes the callers. This means that when optimizing a some
67+
caller function, the optimizer had already optimized all of its callees and the
68+
optimizer can inspect the callee for side effects and inline it into the caller.
69+
70+
The pass manager is also responsible for the registry and invalidation of
71+
analysis. We discuss this topic at length below. The pass manager provides debug
72+
and logging utilities, such as the ability to print the content of the module
73+
after specific optimizations and to measure how much time is spent in
74+
each pass.
75+
76+
77+
Optimization passes
78+
===================
79+
There are two kind of optimization passes in Swift: Function passes, and Module
80+
passes. Function passes can inspect the entire module but can only modify a
81+
single function. Function passes can't control the order in which functions in
82+
the module are being processed - this is the job of the Pass Manager. Most
83+
optimizations are function passes. Optimizations such as LICM and CSE are
84+
function passes because they only modify a single function. Function passes are
85+
free to analyze other functions in the program.
86+
87+
Module passes can scan, view and transform the entire module. Optimizations that
88+
create new functions or that require a specific processing order needs
89+
to be module optimizations. The Generic Specializer pass is a module pass
90+
because it needs to perform a top-down scan of the module in order to propagate
91+
type information down the call graph.
92+
93+
This is the structure of a simple function pass:
94+
95+
::
96+
97+
class CSE : public SILFunctionTransform {
98+
void run() override {
99+
// .. do stuff ..
100+
}
101+
102+
StringRef getName() override {
103+
return "CSE";
104+
}
105+
};
106+
107+
108+
Analysis Invalidation
109+
=====================
110+
111+
Swift Analysis are very different from LLVM analysis. Swift analysis are simply
112+
a cache behind some utility that performs computation. For example, the
113+
dominators analysis is a cache that saves the result of the utility that
114+
computes the dominator tree of function.
115+
116+
Optimization passes can ask the pass manager for a specific analysis by name.
117+
The pass manager will return a pointer to the analysis, and optimization passes
118+
can query the analysis.
119+
120+
The code below requests access to the Dominance analysis.
121+
::
122+
123+
DominanceAnalysis* DA = getAnalysis<DominanceAnalysis>();
124+
125+
126+
Passes that transform the IR are required to invalidate the analysis. However,
127+
optimization passes are not required to know about all the existing analysis.
128+
The mechanism that swift uses to invalidate analysis is broadcast-invalidation.
129+
Passes ask the pass manager to invalidate specific traits. For example, a pass
130+
like simplify-cfg will ask the pass manager to announce that it modified some
131+
branches in the code. The pass manager will send a message to all of the
132+
available analysis that says “please invalidate yourself if you care about
133+
branches for function F“. The dominator tree would then invalidate the dominator
134+
tree for function F because it knows that changes to branches can mean that the
135+
dominator tree was modified.
136+
137+
The code below invalidates sends a message to all of the analysis saying that
138+
some instructions (that are not branches or calls) were modified in the function
139+
that the current function pass is processing.
140+
141+
.. code-block:: cpp
142+
143+
if (Changed) {
144+
invalidateAnalysis(InvalidationKind::Instructions);
145+
}
146+
147+
148+
The code below is a part of an analysis that responds to invalidation messages.
149+
The analysis checks if any calls in the program were modified and invalidates
150+
the cache for the function that was modified.
151+
152+
.. code-block:: cpp
153+
154+
virtual void invalidate(SILFunction *F,
155+
InvalidationKind K) override {
156+
if (K & InvalidationKind::Calls) {
157+
Storage[F].clear();
158+
}
159+
}
160+
161+
162+
The invalidation traits that passes can invalidate are are:
163+
1. Instructions - some instructions were added, deleted or moved.
164+
2. Calls - some call sites were added or deleted.
165+
3. Branches - branches in the code were added, deleted or modified.
166+
4. Functions - Some functions were added or deleted.
167+
168+
Semantic Tags
169+
=============
170+
171+
The Swift optimizer has optimization passes that target specific data structures
172+
in the Swift standard library. For example, one optimization can remove the
173+
Array copy-on-write uniqueness checks and hoist them out of loops. Another
174+
optimization can remove array access bounds checks.
175+
176+
The Swift optimizer can detect code in the standard library if it is marked with
177+
special attributes @_semantics, that identifies the functions.
178+
179+
This is an example of the ``@_semantics`` attribute as used by Swift Array:
180+
181+
::
182+
183+
@public @_semantics("array.count")
184+
func getCount() -> Int {
185+
return _buffer.count
186+
}
187+
188+
Notice that as soon as we inline functions that have the @_semantics attribute
189+
the attribute is lost and the optimizer can't analyze the content of the
190+
function. For example, the optimizer can identify the array ‘count' method (that
191+
returns the size of the array) and can hoist this method out of loops. However,
192+
as soon as this method is inlined, the code looks to the optimizer like a memory
193+
read from an undetermined memory location, and the optimizer can't optimize the
194+
code anymore. In order to work around this problem we prevent the inlining of
195+
functions with the @_semantics attribute until after all of the data-structure
196+
specific optimizations are done. Unfortunately, this lengthens our optimization
197+
pipeline.
198+
199+
Please refer to the document “High-Level SIL Optimizations” for more details.
200+
201+
Debugging the optimizer
202+
=======================
203+
TODO.
204+
205+
Whole Module Optimizations
206+
==========================
207+
TODO.
208+
209+
List of passes
210+
==============
211+
The updated list of passes is available in the file “Passes.def”.

0 commit comments

Comments
 (0)