dan sinclair | eee8d88 | 2022-10-28 01:22:58 +0000 | [diff] [blame] | 1 | # Intermediate Representation |
| 2 | |
| 3 | As Tint has grown the number of transforms on the AST has grown. This |
| 4 | growth has lead to several issues: |
| 5 | |
| 6 | 1. Transforms rebuild the AST and SEM which causes slowness |
| 7 | 1. Transforming in AST can be difficult as the AST is hard to work with |
| 8 | |
dan sinclair | 14440bc | 2023-07-11 19:12:29 +0000 | [diff] [blame] | 9 | In order to address these goals, an IR was introduced into Tint. The IR |
| 10 | is mutable, it holds the needed state in order to be transformed. The IR |
| 11 | is also translatable back into AST. It will be possible to generate an |
| 12 | AST, convert to IR, transform, and then rebuild a new AST. This |
| 13 | round-trip ability provides a few features: |
dan sinclair | eee8d88 | 2022-10-28 01:22:58 +0000 | [diff] [blame] | 14 | |
| 15 | 1. Easy to integrate into current system by replacing AST transforms |
| 16 | piecemeal |
| 17 | 1. Easier to test as the resulting AST can be emitted as WGSL and |
| 18 | compared. |
| 19 | |
| 20 | The IR helps with the complexity of the AST transforms by limiting the |
dan sinclair | 14440bc | 2023-07-11 19:12:29 +0000 | [diff] [blame] | 21 | representations in the IR form. For example, instead of `for`, `while` |
| 22 | and `loop` constructs there is a single `loop` construct. `alias` and |
| 23 | `const_assert` nodes are not emitted into IR. Dead code maybe eliminated |
| 24 | during IR construction. |
dan sinclair | eee8d88 | 2022-10-28 01:22:58 +0000 | [diff] [blame] | 25 | |
| 26 | As the IR can convert into AST, we could potentially simplify the |
| 27 | SPIRV-Reader by generating IR directly. The IR is closer to what SPIR-V |
| 28 | looks like, so maybe a simpler transform. |
| 29 | |
dan sinclair | 14440bc | 2023-07-11 19:12:29 +0000 | [diff] [blame] | 30 | |
dan sinclair | eee8d88 | 2022-10-28 01:22:58 +0000 | [diff] [blame] | 31 | ## Design |
| 32 | |
dan sinclair | 14440bc | 2023-07-11 19:12:29 +0000 | [diff] [blame] | 33 | The IR is composed of three primary concepts, `blocks`, `values`, and |
| 34 | `instructions`. |
dan sinclair | eee8d88 | 2022-10-28 01:22:58 +0000 | [diff] [blame] | 35 | |
dan sinclair | 14440bc | 2023-07-11 19:12:29 +0000 | [diff] [blame] | 36 | The `blocks` provide lists of `instructions` which are executed |
| 37 | sequentially. An `instruction` may contain `blocks`, `value` operands |
| 38 | and `value` results. A `value` is a block argument, function parameter, |
| 39 | constant result, or a result computed by an `instruction`. |
| 40 | |
| 41 | Each `block` ends in a `terminator` expression. There are a few blocks |
| 42 | which maybe empty, `if-false block`, `loop-initializer` and |
| 43 | `loop-continuing`. Those blocks are optional. The `terminators` may hold |
| 44 | `values` which are returned out of the block being terminated. |
| 45 | |
| 46 | Control flow is handled through `ControlInstructions`. These are the |
| 47 | `if`, `switch` and `loop` instructions. Each control instruction |
| 48 | contains `blocks` for the various control flow paths. |
dan sinclair | eee8d88 | 2022-10-28 01:22:58 +0000 | [diff] [blame] | 49 | |
| 50 | Transforming from AST to IR and back to AST is a lossy operation. |
dan sinclair | 14440bc | 2023-07-11 19:12:29 +0000 | [diff] [blame] | 51 | The resulting AST when converting back may not be the same as the |
| 52 | AST being provided. This transformation is intentional as it greatly |
| 53 | simplifies the number of things to consider in the IR. For instance: |
dan sinclair | eee8d88 | 2022-10-28 01:22:58 +0000 | [diff] [blame] | 54 | |
| 55 | * No `alias` nodes |
Ben Clayton | c98d57d | 2023-01-24 14:59:43 +0000 | [diff] [blame] | 56 | * No `const_assert` nodes |
dan sinclair | 14440bc | 2023-07-11 19:12:29 +0000 | [diff] [blame] | 57 | |
dan sinclair | eee8d88 | 2022-10-28 01:22:58 +0000 | [diff] [blame] | 58 | |
| 59 | ### Code Structure |
| 60 | The code is contained in the `src/tint/ir` folder and is broken down |
| 61 | into several classes. Note, the IR is a Tint _internal_ representation |
| 62 | and these files should _never_ appear in the public API. |
| 63 | |
dan sinclair | 14440bc | 2023-07-11 19:12:29 +0000 | [diff] [blame] | 64 | |
dan sinclair | eee8d88 | 2022-10-28 01:22:58 +0000 | [diff] [blame] | 65 | #### Builder |
| 66 | The `Builder` class provides useful helper routines for creating IR |
dan sinclair | 14440bc | 2023-07-11 19:12:29 +0000 | [diff] [blame] | 67 | content. The Builder references an `ir::Module`. |
| 68 | |
dan sinclair | eee8d88 | 2022-10-28 01:22:58 +0000 | [diff] [blame] | 69 | |
| 70 | #### Module |
| 71 | The top level of the IR is the `Module`. The module stores a list of |
dan sinclair | 14440bc | 2023-07-11 19:12:29 +0000 | [diff] [blame] | 72 | `functions`, constant manager, type manager, allocators and various other |
| 73 | bits of information needed by the IR. The `module` _may_ contain a |
| 74 | disassembled representation of the module if the disassembler has been |
| 75 | called. |
dan sinclair | eee8d88 | 2022-10-28 01:22:58 +0000 | [diff] [blame] | 76 | |
dan sinclair | 14440bc | 2023-07-11 19:12:29 +0000 | [diff] [blame] | 77 | A free `ir::FromProgram` method is provided to convert from a |
| 78 | `Program` to an `ir::Module`. A similar `ToProgram` free method is |
| 79 | provided for the reverse conversion. |
dan sinclair | eee8d88 | 2022-10-28 01:22:58 +0000 | [diff] [blame] | 80 | |
dan sinclair | eee8d88 | 2022-10-28 01:22:58 +0000 | [diff] [blame] | 81 | |
| 82 | ### Transforms |
| 83 | Similar to the AST a transform system is available for IR. The transform |
| 84 | has the same setup as the AST (and inherits from the same base transform |
| 85 | class.) |
| 86 | |
dan sinclair | 97c3727 | 2023-07-24 17:11:53 +0000 | [diff] [blame] | 87 | The IR transforms live in `src/tint/lang/core/ir/transform`. These transforms are |
dan sinclair | 14440bc | 2023-07-11 19:12:29 +0000 | [diff] [blame] | 88 | for use by the various IR generator backends. |
dan sinclair | eee8d88 | 2022-10-28 01:22:58 +0000 | [diff] [blame] | 89 | |
dan sinclair | 14440bc | 2023-07-11 19:12:29 +0000 | [diff] [blame] | 90 | Unlike with the AST transforms, the IR transforms know which transforms |
| 91 | must have run before themselves. The transform manager will then verify |
| 92 | that all the required transforms for a given transform have executed. |
dan sinclair | eee8d88 | 2022-10-28 01:22:58 +0000 | [diff] [blame] | 93 | |
dan sinclair | eee8d88 | 2022-10-28 01:22:58 +0000 | [diff] [blame] | 94 | |
dan sinclair | 14440bc | 2023-07-11 19:12:29 +0000 | [diff] [blame] | 95 | ### Validation |
| 96 | The IR contains a validator. The validator checks for common errors |
| 97 | encountered when building IR. The validator is _not_ run in production |
| 98 | as any error is an internal coding error. The validator is executed by |
| 99 | the generators before starting, and after each transform executes in |
| 100 | debug mode. |
dan sinclair | eee8d88 | 2022-10-28 01:22:58 +0000 | [diff] [blame] | 101 | |
dan sinclair | eee8d88 | 2022-10-28 01:22:58 +0000 | [diff] [blame] | 102 | |
dan sinclair | 14440bc | 2023-07-11 19:12:29 +0000 | [diff] [blame] | 103 | ### Naming |
| 104 | The instructions are internal to the IR, and the values in the IR do not |
| 105 | contain names. The IR module has a symbol table which can be used to |
| 106 | retrieve names for the instructions and values but those names may not |
| 107 | be the names provided originally by the AST. |
dan sinclair | eee8d88 | 2022-10-28 01:22:58 +0000 | [diff] [blame] | 108 | |
dan sinclair | eee8d88 | 2022-10-28 01:22:58 +0000 | [diff] [blame] | 109 | |
dan sinclair | 14440bc | 2023-07-11 19:12:29 +0000 | [diff] [blame] | 110 | ### Control Flow |
dan sinclair | eee8d88 | 2022-10-28 01:22:58 +0000 | [diff] [blame] | 111 | |
dan sinclair | 14440bc | 2023-07-11 19:12:29 +0000 | [diff] [blame] | 112 | #### Block |
| 113 | A block contains the instruction lists for a section of code. A block |
| 114 | always ends in a terminator instruction. |
dan sinclair | eee8d88 | 2022-10-28 01:22:58 +0000 | [diff] [blame] | 115 | |
dan sinclair | 14440bc | 2023-07-11 19:12:29 +0000 | [diff] [blame] | 116 | The instructions in a block can be walked in linear scoped fashion. The |
| 117 | control instructions do not need to be entered to walk a blocks |
| 118 | instructions. |
dan sinclair | eee8d88 | 2022-10-28 01:22:58 +0000 | [diff] [blame] | 119 | |
dan sinclair | eee8d88 | 2022-10-28 01:22:58 +0000 | [diff] [blame] | 120 | |
dan sinclair | 14440bc | 2023-07-11 19:12:29 +0000 | [diff] [blame] | 121 | #### Control Instructions |
| 122 | The control instructions provide containers for other blocks. These |
| 123 | include the `if`, `loop` and `switch` instruction. A control instruction |
| 124 | is a self contained scope on top of the containing block scope. |
dan sinclair | eee8d88 | 2022-10-28 01:22:58 +0000 | [diff] [blame] | 125 | |
dan sinclair | eee8d88 | 2022-10-28 01:22:58 +0000 | [diff] [blame] | 126 | |
dan sinclair | 14440bc | 2023-07-11 19:12:29 +0000 | [diff] [blame] | 127 | ##### Control Instruction -- If |
| 128 | The if instruction is an `if-else` structure. There are no `else-if` |
| 129 | entries, they get moved into the `else` of the `if`. The if instruction |
| 130 | has two internal blocks, the `True` and `False` blocks. |
dan sinclair | eee8d88 | 2022-10-28 01:22:58 +0000 | [diff] [blame] | 131 | |
dan sinclair | 14440bc | 2023-07-11 19:12:29 +0000 | [diff] [blame] | 132 | A if instruction will _always_ have a `True` target. The `False` target |
| 133 | maybe empty. |
dan sinclair | eee8d88 | 2022-10-28 01:22:58 +0000 | [diff] [blame] | 134 | |
dan sinclair | 14440bc | 2023-07-11 19:12:29 +0000 | [diff] [blame] | 135 | The sub-blocks under the `if` can be terminated with `ExitIf`, |
| 136 | `ExitSwitch`, `ExitLoop`, `Continue` or `Return`. |
dan sinclair | eee8d88 | 2022-10-28 01:22:58 +0000 | [diff] [blame] | 137 | |
dan sinclair | eee8d88 | 2022-10-28 01:22:58 +0000 | [diff] [blame] | 138 | |
dan sinclair | 14440bc | 2023-07-11 19:12:29 +0000 | [diff] [blame] | 139 | #### Control Instruction -- Loop |
| 140 | All of the loop structures in AST merge down to a single loop |
| 141 | instruction. The loop contains the `Body`, which is required and |
| 142 | optional `Initializer` and `Continuing` blocks. |
dan sinclair | eee8d88 | 2022-10-28 01:22:58 +0000 | [diff] [blame] | 143 | |
dan sinclair | 14440bc | 2023-07-11 19:12:29 +0000 | [diff] [blame] | 144 | The `loop` body can be terminated with a `ExitLoop`, `Continue`, |
| 145 | `NextIteration` or `Return`. |
dan sinclair | eee8d88 | 2022-10-28 01:22:58 +0000 | [diff] [blame] | 146 | |
dan sinclair | 14440bc | 2023-07-11 19:12:29 +0000 | [diff] [blame] | 147 | The `loop` continuing can be terminated with a `NextIteration`, or a |
| 148 | `BreakIf` terminator. |
dan sinclair | eee8d88 | 2022-10-28 01:22:58 +0000 | [diff] [blame] | 149 | |
| 150 | A while loop is decomposed as listed in the WGSL spec: |
| 151 | |
| 152 | ``` |
| 153 | while (a < b) { |
| 154 | c += 1; |
| 155 | } |
| 156 | ``` |
| 157 | |
| 158 | becomes: |
| 159 | |
| 160 | ``` |
| 161 | loop { |
| 162 | if (!(a < b)) { |
| 163 | break; |
| 164 | } |
| 165 | c += 1; |
| 166 | } |
| 167 | ``` |
| 168 | |
| 169 | A for loop is decomposed as listed in the WGSL spec: |
| 170 | ``` |
| 171 | for (var i = 0; i < 10; i++) { |
| 172 | c += 1; |
| 173 | } |
| 174 | ``` |
| 175 | |
| 176 | becomes: |
| 177 | |
| 178 | ``` |
| 179 | var i = 0; |
| 180 | loop { |
| 181 | if (!(i < 10)) { |
| 182 | break; |
| 183 | } |
| 184 | |
| 185 | c += 1; |
| 186 | |
| 187 | continuing { |
| 188 | i++; |
| 189 | } |
| 190 | } |
| 191 | ``` |
| 192 | |
dan sinclair | 14440bc | 2023-07-11 19:12:29 +0000 | [diff] [blame] | 193 | #### Control Instruction -- Switch |
| 194 | The switch instruction has a block for each of the `case/default` labels. |
dan sinclair | eee8d88 | 2022-10-28 01:22:58 +0000 | [diff] [blame] | 195 | |
dan sinclair | 14440bc | 2023-07-11 19:12:29 +0000 | [diff] [blame] | 196 | The `switch` case blocks can be terminated with an `ExitSwitch`, |
| 197 | `Continue`, or `Return`. |
dan sinclair | eee8d88 | 2022-10-28 01:22:58 +0000 | [diff] [blame] | 198 | |
dan sinclair | eee8d88 | 2022-10-28 01:22:58 +0000 | [diff] [blame] | 199 | |
| 200 | #### Expressions |
| 201 | All expressions in IR are single operations. There are no complex |
| 202 | expressions. Any complex expression in the AST is broke apart into the |
| 203 | simpler single operation components. |
| 204 | |
| 205 | ``` |
| 206 | var a = b + c - (4 * k); |
| 207 | ``` |
| 208 | |
| 209 | becomes: |
| 210 | |
| 211 | ``` |
dan sinclair | 14440bc | 2023-07-11 19:12:29 +0000 | [diff] [blame] | 212 | %0:i32 = add %b, %c |
| 213 | %1:i32 = mul 4u, %k |
| 214 | %2:i32 = sub %0, %1 |
| 215 | %v:ptr<function, i32, read_write> = var %2 |
dan sinclair | eee8d88 | 2022-10-28 01:22:58 +0000 | [diff] [blame] | 216 | ``` |
| 217 | |
| 218 | This also means that many of the short forms `i += 1`, `i++` get |
| 219 | expanded into the longer form of `i = i + 1`. |
| 220 | |
dan sinclair | 14440bc | 2023-07-11 19:12:29 +0000 | [diff] [blame] | 221 | |
dan sinclair | eee8d88 | 2022-10-28 01:22:58 +0000 | [diff] [blame] | 222 | ##### Short-Circuit Expressions |
dan sinclair | 14440bc | 2023-07-11 19:12:29 +0000 | [diff] [blame] | 223 | The short-circuit expressions (e.g. `a && b`) are convert into an `if` |
| 224 | structure control flow. |
dan sinclair | eee8d88 | 2022-10-28 01:22:58 +0000 | [diff] [blame] | 225 | |
| 226 | ``` |
| 227 | let c = a() && b() |
| 228 | ``` |
| 229 | |
| 230 | becomes |
| 231 | |
| 232 | ``` |
| 233 | let c = a(); |
| 234 | if (c) { |
| 235 | c = b(); |
| 236 | } |
| 237 | ``` |
| 238 | |
dan sinclair | 14440bc | 2023-07-11 19:12:29 +0000 | [diff] [blame] | 239 | #### Values |
| 240 | There are several types of values used in the SSA form. |
dan sinclair | eee8d88 | 2022-10-28 01:22:58 +0000 | [diff] [blame] | 241 | |
dan sinclair | 14440bc | 2023-07-11 19:12:29 +0000 | [diff] [blame] | 242 | 1. `Constant` Value |
| 243 | 1. `InstructionResult` Value |
| 244 | 1. `Function` Value |
| 245 | 1. `FunctionParam` Value |
| 246 | 1. `BlockParam` Value |
dan sinclair | eee8d88 | 2022-10-28 01:22:58 +0000 | [diff] [blame] | 247 | |
dan sinclair | eee8d88 | 2022-10-28 01:22:58 +0000 | [diff] [blame] | 248 | |
dan sinclair | 14440bc | 2023-07-11 19:12:29 +0000 | [diff] [blame] | 249 | ##### Constant Value |
| 250 | All values in IR are concrete, there are no abstract values as |
| 251 | materialization has already happened. Each constant holds a lower level |
dan sinclair | 97c3727 | 2023-07-24 17:11:53 +0000 | [diff] [blame] | 252 | `constant::Value` from the `src/tint/lang/core/ir/constant` system. |
dan sinclair | 14440bc | 2023-07-11 19:12:29 +0000 | [diff] [blame] | 253 | |
| 254 | |
| 255 | ##### InstructionResult Value |
| 256 | The `InstructionResult` is the result of an instruction. The |
dan sinclair | eee8d88 | 2022-10-28 01:22:58 +0000 | [diff] [blame] | 257 | temporaries are created as complex expressions are broken down into |
dan sinclair | 14440bc | 2023-07-11 19:12:29 +0000 | [diff] [blame] | 258 | pieces. The result tracks the usage for the value so you can determine |
| 259 | which instructions use this value. The `InstructionResult` also points |
| 260 | back to its `Source` instruction. |
dan sinclair | eee8d88 | 2022-10-28 01:22:58 +0000 | [diff] [blame] | 261 | |
dan sinclair | eee8d88 | 2022-10-28 01:22:58 +0000 | [diff] [blame] | 262 | |
dan sinclair | 14440bc | 2023-07-11 19:12:29 +0000 | [diff] [blame] | 263 | ### Function Value |
| 264 | All `Functions` are values in the IR. This allows the function to be |
| 265 | provided as a Value argument to things like the `Call` instructions. |
dan sinclair | eee8d88 | 2022-10-28 01:22:58 +0000 | [diff] [blame] | 266 | |
dan sinclair | eee8d88 | 2022-10-28 01:22:58 +0000 | [diff] [blame] | 267 | |
dan sinclair | 14440bc | 2023-07-11 19:12:29 +0000 | [diff] [blame] | 268 | ##### FunctionParam Value |
| 269 | The function param values are used to store information on the values |
| 270 | being passed into a function call. |
dan sinclair | eee8d88 | 2022-10-28 01:22:58 +0000 | [diff] [blame] | 271 | |
dan sinclair | 14440bc | 2023-07-11 19:12:29 +0000 | [diff] [blame] | 272 | ### BlockParam Values |
| 273 | The `BlockParam` values are used to pass information for a |
| 274 | `MultiInBlock` (e.g. Loop body, and loop continuing). |
dan sinclair | eee8d88 | 2022-10-28 01:22:58 +0000 | [diff] [blame] | 275 | |
| 276 | ## Alternatives |
| 277 | Instead of going to a custom IR there are several possible other roads |
| 278 | that could be travelled. |
| 279 | |
| 280 | ### Mutable AST |
| 281 | Tint originally contained a mutable AST. This was converted to immutable |
| 282 | in order to allow processing over multiple threads and for safety |
| 283 | properties. Those desires still hold, the AST is public API, and we want |
| 284 | it to be as safe as possible, so keeping it immutable provides that |
| 285 | guarantee. |
| 286 | |
| 287 | ### Multiple Transforms With One Program Builder |
| 288 | Instead of generating an immutable AST after each transform, running |
| 289 | multiple transforms on the single program builder would remove some of |
| 290 | the performance penalties of going to and from immutable AST. While this |
| 291 | is true, the transforms use a combination of AST and SEM information. |
| 292 | When they transform they _do not_ create new SEM information. That |
| 293 | means, after a given transform, the SEM is out of date. In order to |
| 294 | re-generate the SEM the resolver needs to be rerun. Supporting this |
| 295 | would require being very careful on what transforms run together and |
| 296 | how they modify the AST. |
| 297 | |
| 298 | ### Adopt An Existing IR |
| 299 | There are already several IRs in the while, Mesa has NIR, LLVM has |
| 300 | LLVM IR. There are others, adopting one of those would remove the |
| 301 | requirements of writing and maintaining our own IR. While that is true, |
| 302 | there are several downsides to this re-use. The IRs are internal to the |
| 303 | library, so the API isn't public, LLVM IR changes with each iteration of |
| 304 | LLVM. This would require us to adapt the AST -> IR -> AST transform for |
| 305 | each modification of the IR. |
| 306 | |
| 307 | They also end up being lower level then is strictly useful for us. While |
| 308 | the IR in Tint is a simplified form, we still have to be able to go back |
| 309 | to the high level structured form in order to emit the resulting HLSL, |
| 310 | MSL, GLSL, etc. (Only SPIR-V is a good match for the lowered IR form). |
| 311 | This transformation back is not a direction other IRs maybe interested |
| 312 | in so may have lost information, or require re-determining (determining |
| 313 | variables from SSA and PHI nodes for example). |
| 314 | |
| 315 | Other technical reasons are the maintenance of BUILD.gn and CMake files |
| 316 | in order to integrate into our build systems, along with resulting |
| 317 | binary size questions from pulling in external systems. |
| 318 | |