Files
swift-mirror/test/SILOptimizer/mandatory-dead-object-elimination.sil
Erik Eckstein 8c9a5ba1b6 Optimizer: rewrite the DeadObjectElimination pass
DeadObjectElimination  eliminates dead `alloc_stack` and `alloc_ref` allocations.

**Stack allocations**

A stack allocation is dead if its stored values never escape and all loads can be forwarded directly to the stored values:

```
  %s = alloc_stack $Foo
  store %v to [init] %s : $*Foo
  %l = load [take] %s : $*Foo
  dealloc_stack %s : $*Foo
```
->
```
  // %l is replaced with %v
```

This also handles field-level stores and loads. Destroy instructions for field values whose containing object is destroyed replace the original aggregate destroy:

```
  %s = alloc_stack $Foo
  %fa = struct_element_addr %s : $*Foo, #Foo.x
  store %x to [init] %fa : $*Int
  destroy_addr %s : $*Foo
  dealloc_stack %s : $*Foo
```
->
```
  destroy_value %x : $Int
```

**Heap allocations**

A heap allocation is dead if it never escapes and its destructor has no observable side effects. Stored field values are destroyed in place of the object destructor:

```
  %obj = alloc_ref $Foo
  %addr = ref_element_addr %obj : $Foo, #Foo.x
  store %x to [init] %addr : $*Int
  destroy_value %obj : $Foo
```
->
```
  destroy_value %x : $Int
```

The pass replaces the old DeadObjectElimination and SILMem2Reg. It combines the advantages of both passes:

* The old DeadObjectElimination can only remove an `alloc_stack` or `alloc_ref` if there are no loads from such allocations.

* SILMem2Reg can handle loads, but can only remove `alloc_stack`s and not `alloc_ref`s.

The new pass can handle loads from both, `alloc_stack` and `alloc_ref`.
This means, for example, it can do array element propagation (an array buffer is an `alloc_ref) and, if there are no other uses of the array,
completely eliminate the array.

Also, the new pass has other improvements compared to the old passes. E.g. it can deal with objects where the individual fields are initialized with separate stores.

This change also adds the pass variant MandatoryDeadObjectElimination. It replaces the old PredictableDeadAllocationElimination pass.
2026-07-13 10:04:16 +02:00

89 lines
2.8 KiB
Plaintext

// RUN: %target-sil-opt -mandatory-dead-object-elimination %s | %FileCheck %s
import Builtin
import Swift
class Klass {
deinit {}
}
// Trivial deinit for Klass: just deallocates, no side effects.
sil [ossa] @$s4main5KlassCfD : $@convention(method) (@owned Klass) -> () {
bb0(%0 : @owned $Klass):
dealloc_ref %0 : $Klass
%r = tuple ()
return %r : $()
}
// Non-lexical alloc_stack with no debug variable is eliminated.
//
// CHECK-LABEL: sil [ossa] @basic_elimination :
// CHECK-NOT: alloc_stack
// CHECK: return %0
// CHECK-LABEL: } // end sil function 'basic_elimination'
sil [ossa] @basic_elimination : $@convention(thin) (@owned Klass) -> @owned Klass {
bb0(%0 : @owned $Klass):
%stk = alloc_stack $Klass
store %0 to [init] %stk : $*Klass
%r = load [take] %stk : $*Klass
dealloc_stack %stk : $*Klass
return %r : $Klass
}
// Lexical alloc_stack of non-trivial type is preserved for debug info.
//
// CHECK-LABEL: sil [ossa] @preserve_lexical_nontrivial :
// CHECK: alloc_stack [lexical]
// CHECK-LABEL: } // end sil function 'preserve_lexical_nontrivial'
sil [ossa] @preserve_lexical_nontrivial : $@convention(thin) (@owned Klass) -> @owned Klass {
bb0(%0 : @owned $Klass):
%stk = alloc_stack [lexical] $Klass
store %0 to [init] %stk : $*Klass
%r = load [take] %stk : $*Klass
dealloc_stack %stk : $*Klass
return %r : $Klass
}
// Lexical alloc_stack of trivial type is eligible for elimination: trivial
// types carry no ownership and don't need to be kept around for debug info.
//
// CHECK-LABEL: sil [ossa] @eliminate_lexical_trivial :
// CHECK-NOT: alloc_stack
// CHECK: return %0
// CHECK-LABEL: } // end sil function 'eliminate_lexical_trivial'
sil [ossa] @eliminate_lexical_trivial : $@convention(thin) (Int) -> Int {
bb0(%0 : $Int):
%stk = alloc_stack [lexical] $Int
store %0 to [trivial] %stk : $*Int
%r = load [trivial] %stk : $*Int
dealloc_stack %stk : $*Int
return %r : $Int
}
// alloc_ref is not touched by this pass — only alloc_stack allocations are considered.
//
// CHECK-LABEL: sil [ossa] @preserve_alloc_ref :
// CHECK: alloc_ref $Klass
// CHECK-LABEL: } // end sil function 'preserve_alloc_ref'
sil [ossa] @preserve_alloc_ref : $@convention(thin) () -> () {
bb0:
%obj = alloc_ref $Klass
destroy_value %obj : $Klass
%r = tuple ()
return %r : $()
}
// alloc_stack with an associated debug variable is preserved even if non-lexical.
//
// CHECK-LABEL: sil [ossa] @preserve_debug_var :
// CHECK: alloc_stack
// CHECK-LABEL: } // end sil function 'preserve_debug_var'
sil [ossa] @preserve_debug_var : $@convention(thin) (@owned Klass) -> @owned Klass {
bb0(%0 : @owned $Klass):
%stk = alloc_stack $Klass, var, name "k"
store %0 to [init] %stk : $*Klass
%r = load [take] %stk : $*Klass
dealloc_stack %stk : $*Klass
return %r : $Klass
}