|
| 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