|
6 | 6 |
|
7 | 7 | namespace Slang
|
8 | 8 | {
|
9 |
| - |
10 | 9 | struct DeadCodeEliminationContext
|
11 | 10 | {
|
12 | 11 | // This type implements a simple global DCE pass over
|
@@ -158,20 +157,8 @@ struct DeadCodeEliminationContext
|
158 | 157 | // "weak" references -- they can never hold things alive, and
|
159 | 158 | // whenever we delete the referenced value, these operands needs
|
160 | 159 | // to be replaced with `undef`.
|
161 |
| - switch (inst->getOp()) |
162 |
| - { |
163 |
| - case kIROp_BoundInterfaceType: |
164 |
| - if (inst->getOperand(ii)->getOp() == kIROp_WitnessTable) |
165 |
| - continue; |
166 |
| - break; |
167 |
| - case kIROp_SpecializationDictionaryItem: |
168 |
| - // Ignore all operands of SpecializationDictionaryItem. |
169 |
| - // This inst is used as a cache and shouldn't hold anything alive. |
170 |
| - continue; |
171 |
| - default: |
172 |
| - break; |
173 |
| - } |
174 |
| - markInstAsLive(inst->getOperand(ii)); |
| 160 | + if (!isWeakReferenceOperand(inst, ii)) |
| 161 | + markInstAsLive(inst->getOperand(ii)); |
175 | 162 | }
|
176 | 163 |
|
177 | 164 | // Finally, we need to consider the children
|
@@ -267,119 +254,146 @@ struct DeadCodeEliminationContext
|
267 | 254 | //
|
268 | 255 | bool shouldInstBeLiveIfParentIsLive(IRInst* inst)
|
269 | 256 | {
|
270 |
| - // The main source of confusion/complexity here is that |
271 |
| - // we are using the same routine to decide: |
272 |
| - // |
273 |
| - // * Should some ordinary instruction in a basic block be kept around? |
274 |
| - // * Should a basic block in some function be kept around? |
275 |
| - // * Should a function/type/variable in a module be kept around? |
276 |
| - // |
277 |
| - // Still, there are a few basic patterns we can observe. |
278 |
| - // First, if `inst` is an instruction that might have some effects |
279 |
| - // when it is executed, then we should keep it around. |
280 |
| - // |
281 |
| - if(inst->mightHaveSideEffects()) |
282 |
| - return true; |
283 |
| - // |
284 |
| - // The `mightHaveSideEffects` query is conservative, and will |
285 |
| - // return `true` as its default mode, so once we are past that |
286 |
| - // query we know that `inst` is either something "structural" |
287 |
| - // (that makes up the program) rather than executable, or it |
288 |
| - // is executable but was on an allow-list of things that are |
289 |
| - // safe to eliminate. |
| 257 | + return Slang::shouldInstBeLiveIfParentIsLive(inst, options); |
| 258 | + } |
| 259 | +}; |
290 | 260 |
|
291 |
| - // Most top-level objects (functions, types, etc.) obviously |
292 |
| - // do *not* have side effects. That creates the risk that |
293 |
| - // we'll just go ahead and eliminate every single function/type |
294 |
| - // in a module. There needs to be a way to identify the |
295 |
| - // functions we want to keep around, and for right now |
296 |
| - // that is handled with the `[keepAlive]` decoration. |
297 |
| - // |
298 |
| - if(inst->findDecorationImpl(kIROp_KeepAliveDecoration)) |
299 |
| - return true; |
300 |
| - // |
301 |
| - // We also consider anything with an `[export(...)]` as live, |
302 |
| - // when the appropriate option has been set. |
303 |
| - // |
304 |
| - // Note: our current approach to linking for back-end compilation |
305 |
| - // leaves many linakge decorations in place that we seemingly |
306 |
| - // don't need/want, so this option currently can't be enabled |
307 |
| - // unconditionally. |
308 |
| - // |
309 |
| - if( options.keepExportsAlive ) |
310 |
| - { |
311 |
| - if( inst->findDecoration<IRExportDecoration>() ) |
312 |
| - { |
313 |
| - return true; |
314 |
| - } |
315 |
| - } |
| 261 | +bool shouldInstBeLiveIfParentIsLive(IRInst* inst, IRDeadCodeEliminationOptions options) |
| 262 | +{ |
| 263 | + // The main source of confusion/complexity here is that |
| 264 | + // we are using the same routine to decide: |
| 265 | + // |
| 266 | + // * Should some ordinary instruction in a basic block be kept around? |
| 267 | + // * Should a basic block in some function be kept around? |
| 268 | + // * Should a function/type/variable in a module be kept around? |
| 269 | + // |
| 270 | + // Still, there are a few basic patterns we can observe. |
| 271 | + // First, if `inst` is an instruction that might have some effects |
| 272 | + // when it is executed, then we should keep it around. |
| 273 | + // |
| 274 | + if (inst->mightHaveSideEffects()) |
| 275 | + return true; |
| 276 | + // |
| 277 | + // The `mightHaveSideEffects` query is conservative, and will |
| 278 | + // return `true` as its default mode, so once we are past that |
| 279 | + // query we know that `inst` is either something "structural" |
| 280 | + // (that makes up the program) rather than executable, or it |
| 281 | + // is executable but was on an allow-list of things that are |
| 282 | + // safe to eliminate. |
316 | 283 |
|
317 |
| - if (options.keepLayoutsAlive && inst->findDecoration<IRLayoutDecoration>()) |
| 284 | + // Most top-level objects (functions, types, etc.) obviously |
| 285 | + // do *not* have side effects. That creates the risk that |
| 286 | + // we'll just go ahead and eliminate every single function/type |
| 287 | + // in a module. There needs to be a way to identify the |
| 288 | + // functions we want to keep around, and for right now |
| 289 | + // that is handled with the `[keepAlive]` decoration. |
| 290 | + // |
| 291 | + if (inst->findDecorationImpl(kIROp_KeepAliveDecoration)) |
| 292 | + return true; |
| 293 | + // |
| 294 | + // We also consider anything with an `[export(...)]` as live, |
| 295 | + // when the appropriate option has been set. |
| 296 | + // |
| 297 | + // Note: our current approach to linking for back-end compilation |
| 298 | + // leaves many linakge decorations in place that we seemingly |
| 299 | + // don't need/want, so this option currently can't be enabled |
| 300 | + // unconditionally. |
| 301 | + // |
| 302 | + if (options.keepExportsAlive) |
| 303 | + { |
| 304 | + if (inst->findDecoration<IRExportDecoration>()) |
318 | 305 | {
|
319 | 306 | return true;
|
320 | 307 | }
|
| 308 | + } |
321 | 309 |
|
322 |
| - // A basic block is an interesting case. Knowing that a function |
323 |
| - // is live means that its entry block is live, but the liveness |
324 |
| - // of any other blocks is determined by whether they are referenced |
325 |
| - // by other instructions (e.g., a branch from one block to |
326 |
| - // another). |
| 310 | + if (options.keepLayoutsAlive && inst->findDecoration<IRLayoutDecoration>()) |
| 311 | + { |
| 312 | + return true; |
| 313 | + } |
| 314 | + |
| 315 | + // A basic block is an interesting case. Knowing that a function |
| 316 | + // is live means that its entry block is live, but the liveness |
| 317 | + // of any other blocks is determined by whether they are referenced |
| 318 | + // by other instructions (e.g., a branch from one block to |
| 319 | + // another). |
| 320 | + // |
| 321 | + if (auto block = as<IRBlock>(inst)) |
| 322 | + { |
| 323 | + // To determine whether this is the first block in its |
| 324 | + // parent function (or what-have-you) we can simply |
| 325 | + // check if there is a previous block before it. |
327 | 326 | //
|
328 |
| - if( auto block = as<IRBlock>(inst) ) |
329 |
| - { |
330 |
| - // To determine whether this is the first block in its |
331 |
| - // parent function (or what-have-you) we can simply |
332 |
| - // check if there is a previous block before it. |
333 |
| - // |
334 |
| - auto prevBlock = block->getPrevBlock(); |
335 |
| - return prevBlock == nullptr; |
336 |
| - } |
| 327 | + auto prevBlock = block->getPrevBlock(); |
| 328 | + return prevBlock == nullptr; |
| 329 | + } |
337 | 330 |
|
338 |
| - // There are a few special cases of "structural" instructions |
339 |
| - // that we don't want to eliminate, so we'll check for those next. |
| 331 | + // There are a few special cases of "structural" instructions |
| 332 | + // that we don't want to eliminate, so we'll check for those next. |
| 333 | + // |
| 334 | + switch (inst->getOp()) |
| 335 | + { |
| 336 | + // Function parameters obviously shouldn't get eliminated, |
| 337 | + // even if nothing references them, and block parameters |
| 338 | + // (phi nodes) will be considered live when their block is, |
| 339 | + // just so that we don't have to deal with any complications |
| 340 | + // around re-writing the relevant inter-block argument passing. |
340 | 341 | //
|
341 |
| - switch( inst->getOp() ) |
342 |
| - { |
343 |
| - // Function parameters obviously shouldn't get eliminated, |
344 |
| - // even if nothing references them, and block parameters |
345 |
| - // (phi nodes) will be considered live when their block is, |
346 |
| - // just so that we don't have to deal with any complications |
347 |
| - // around re-writing the relevant inter-block argument passing. |
348 |
| - // |
349 |
| - // TODO: A smarter DCE pass could deal with this case more |
350 |
| - // carefully, or we could improve the interprocedural SCCP |
351 |
| - // pass to deal with block parameters instead. |
352 |
| - // |
353 |
| - case kIROp_Param: |
354 |
| - return true; |
| 342 | + // TODO: A smarter DCE pass could deal with this case more |
| 343 | + // carefully, or we could improve the interprocedural SCCP |
| 344 | + // pass to deal with block parameters instead. |
| 345 | + // |
| 346 | + case kIROp_Param: |
| 347 | + return true; |
355 | 348 |
|
356 |
| - // IR struct types and witness tables are currently kludged |
357 |
| - // so that they have child instructions that represent their |
358 |
| - // entries (effectively `(key,value)` pairs), and those child |
359 |
| - // instructions are never directly referenced (e.g., an access |
360 |
| - // to a struct field references the *key* but not the `(key,value)` |
361 |
| - // pair that is the `IRField` instruction. |
362 |
| - // |
363 |
| - // TODO: at some point the IR should use a different representation |
364 |
| - // for struct types and witness tables that does away with |
365 |
| - // this problem. |
366 |
| - // |
367 |
| - case kIROp_StructField: |
368 |
| - case kIROp_WitnessTableEntry: |
369 |
| - return true; |
| 349 | + // IR struct types and witness tables are currently kludged |
| 350 | + // so that they have child instructions that represent their |
| 351 | + // entries (effectively `(key,value)` pairs), and those child |
| 352 | + // instructions are never directly referenced (e.g., an access |
| 353 | + // to a struct field references the *key* but not the `(key,value)` |
| 354 | + // pair that is the `IRField` instruction. |
| 355 | + // |
| 356 | + // TODO: at some point the IR should use a different representation |
| 357 | + // for struct types and witness tables that does away with |
| 358 | + // this problem. |
| 359 | + // |
| 360 | + case kIROp_StructField: |
| 361 | + case kIROp_WitnessTableEntry: |
| 362 | + return true; |
370 | 363 |
|
371 |
| - default: |
372 |
| - break; |
373 |
| - } |
| 364 | + default: |
| 365 | + break; |
| 366 | + } |
374 | 367 |
|
375 |
| - // If none of the explicit cases above matched, then we will consider |
376 |
| - // the instruction to not be live just because its parent is. Further |
377 |
| - // analysis could still lead to a change in the status of `inst`, if |
378 |
| - // an instruction that uses it as an operand is marked live. |
379 |
| - // |
380 |
| - return false; |
| 368 | + // If none of the explicit cases above matched, then we will consider |
| 369 | + // the instruction to not be live just because its parent is. Further |
| 370 | + // analysis could still lead to a change in the status of `inst`, if |
| 371 | + // an instruction that uses it as an operand is marked live. |
| 372 | + // |
| 373 | + return false; |
| 374 | +} |
| 375 | + |
| 376 | +bool isWeakReferenceOperand(IRInst* inst, UInt operandIndex) |
| 377 | +{ |
| 378 | + // There are some type of operands that needs to be treated as |
| 379 | + // "weak" references -- they can never hold things alive, and |
| 380 | + // whenever we delete the referenced value, these operands needs |
| 381 | + // to be replaced with `undef`. |
| 382 | + switch (inst->getOp()) |
| 383 | + { |
| 384 | + case kIROp_BoundInterfaceType: |
| 385 | + if (inst->getOperand(operandIndex)->getOp() == kIROp_WitnessTable) |
| 386 | + return true; |
| 387 | + break; |
| 388 | + case kIROp_SpecializationDictionaryItem: |
| 389 | + // Ignore all operands of SpecializationDictionaryItem. |
| 390 | + // This inst is used as a cache and shouldn't hold anything alive. |
| 391 | + return true; |
| 392 | + default: |
| 393 | + break; |
381 | 394 | }
|
382 |
| -}; |
| 395 | + return false; |
| 396 | +} |
383 | 397 |
|
384 | 398 | // The top-level function for invoking the DCE pass
|
385 | 399 | // is straighforward. We set up the context object
|
|
0 commit comments