Files
swift-mirror/test/SILOptimizer/dead-access-scope-elimination.sil
Erik Eckstein 9a124742b0 Optimizer: add the DeadAccessScopeElimination optimization pass
It eliminates dead access scopes if they are not conflicting with other scopes.

Removes:
```
  %2 = begin_access [modify] [dynamic] %1
  ...                                       // no uses of %2
  end_access %2
```

However, dead _conflicting_ access scopes are not removed.
If a conflicting scope becomes dead because an optimization e.g. removed a load, it is still important to get an access violation at runtime.
Even a propagated value of a redundant load from a conflicting scope is undefined.

```
  %2 = begin_access [modify] [dynamic] %1
  store %x to %2
  %3 = begin_access [read] [dynamic] %1    // conflicting with %2!
  %y = load %3
  end_access %3
  end_access %2
  use(%y)
```
After redundant-load-elimination:
```
  %2 = begin_access [modify] [dynamic] %1
  store %x to %2
  %3 = begin_access [read] [dynamic] %1    // now dead, but still conflicting with %2
  end_access %3
  end_access %2
  use(%x)                                  // propagated from the store, but undefined here!
```
In this case the scope `%3` is not removed because it's important to get an access violation error at runtime before the undefined value `%x` is used.

This pass considers potential conflicting access scopes in called functions.
But it does not consider potential conflicting access in callers (because it can't!).
However, optimizations, like redundant-load-elimination, can only do such transformations if the outer access scope is within the function, e.g.

```
bb0(%0 : $*T):     // an inout from a conflicting scope in the caller
  store %x to %0
  %3 = begin_access [read] [dynamic] %1
  %y = load %3     // cannot be propagated because it cannot be proved that %1 is the same address as %0
  end_access %3
```

All those checks are only done for dynamic access scopes, because they matter for runtime exclusivity checking.
Dead static scopes are removed unconditionally.
2025-11-24 14:49:45 +01:00

310 lines
8.2 KiB
Plaintext

// RUN: %target-sil-opt %s -dead-access-scope-elimination | %FileCheck %s
sil_stage canonical
import Swift
import Builtin
sil_global @g1: $Int
sil_global @g2: $Int
sil @unknown : $@convention(thin) () -> ()
sil @readonly : $@convention(thin) () -> () {
[global: read]
}
sil @pure : $@convention(thin) () -> () {
[global: ]
}
// CHECK-LABEL: sil [ossa] @simple_dead :
// CHECK-NOT: begin_access
// CHECK-NOT: end_access
// CHECK: } // end sil function 'simple_dead'
sil [ossa] @simple_dead : $@convention(thin) () -> () {
bb0:
%0 = global_addr @g1 : $*Int
%2 = begin_access [modify] [dynamic] %0
fix_lifetime %2
debug_value %2 : $*Int, name "g1"
end_access %2
%r = tuple ()
return %r
}
// CHECK-LABEL: sil [ossa] @simple_alive :
// CHECK: [[A:%.*]] = begin_access
// CHECK: [[L:%.*]] = load [trivial] [[A]]
// CHECK: return [[L]]
// CHECK: } // end sil function 'simple_alive'
sil [ossa] @simple_alive : $@convention(thin) () -> Int {
bb0:
%0 = global_addr @g1 : $*Int
%2 = begin_access [modify] [dynamic] %0
%3 = load [trivial] %2
end_access %2
return %3
}
// CHECK-LABEL: sil [ossa] @conflicting :
// CHECK: %1 = begin_access [modify]
// CHECK: %2 = begin_access [read]
// CHECK: end_access %2
// CHECK: end_access %1
// CHECK: } // end sil function 'conflicting'
sil [ossa] @conflicting : $@convention(thin) () -> () {
bb0:
%0 = global_addr @g1 : $*Int
%1 = begin_access [modify] [dynamic] %0
%2 = begin_access [read] [dynamic] %0
end_access %2
end_access %1
%r = tuple ()
return %r
}
// CHECK-LABEL: sil [ossa] @not_conflicting :
// CHECK-NOT: begin_access
// CHECK-NOT: end_access
// CHECK: } // end sil function 'not_conflicting'
sil [ossa] @not_conflicting : $@convention(thin) () -> () {
bb0:
%0 = global_addr @g1 : $*Int
%1 = begin_access [read] [dynamic] %0
%2 = begin_access [read] [dynamic] %0
end_access %2
end_access %1
%r = tuple ()
return %r
}
// CHECK-LABEL: sil [ossa] @not_aliasing :
// CHECK-NOT: begin_access
// CHECK-NOT: end_access
// CHECK: } // end sil function 'not_aliasing'
sil [ossa] @not_aliasing : $@convention(thin) () -> () {
bb0:
%0 = global_addr @g1 : $*Int
%1 = global_addr @g2 : $*Int
%2 = begin_access [modify] [dynamic] %0
%3 = begin_access [read] [dynamic] %1
end_access %3
end_access %2
%r = tuple ()
return %r
}
// CHECK-LABEL: sil [ossa] @call_with_potential_access :
// CHECK: %2 = begin_access [read]
// CHECK: %3 = begin_access [read]
// CHECK: end_access %3
// CHECK: end_access %2
// CHECK: } // end sil function 'call_with_potential_access'
sil [ossa] @call_with_potential_access : $@convention(thin) () -> () {
bb0:
%0 = global_addr @g1 : $*Int
%1 = global_addr @g2 : $*Int
%2 = begin_access [read] [dynamic] %0
%3 = begin_access [read] [dynamic] %1
%4 = function_ref @unknown : $@convention(thin) () -> ()
%5 = apply %4() : $@convention(thin) () -> ()
end_access %3
end_access %2
%r = tuple ()
return %r
}
// CHECK-LABEL: sil [ossa] @call_pure :
// CHECK-NOT: begin_access
// CHECK-NOT: end_access
// CHECK: } // end sil function 'call_pure'
sil [ossa] @call_pure : $@convention(thin) () -> () {
bb0:
%0 = global_addr @g1 : $*Int
%1 = global_addr @g2 : $*Int
%2 = begin_access [read] [dynamic] %0
%3 = begin_access [read] [dynamic] %1
%4 = function_ref @pure : $@convention(thin) () -> ()
%5 = apply %4() : $@convention(thin) () -> ()
end_access %3
end_access %2
%r = tuple ()
return %r
}
// CHECK-LABEL: sil [ossa] @call_readonly :
// CHECK: %3 = begin_access
// CHECK-NEXT: apply
// CHECK-NEXT: end_access %3
// CHECK-NEXT: apply
// CHECK-NEXT: tuple
// CHECK: } // end sil function 'call_readonly'
sil [ossa] @call_readonly : $@convention(thin) () -> () {
bb0:
%0 = global_addr @g1 : $*Int
%1 = global_addr @g2 : $*Int
%2 = function_ref @readonly : $@convention(thin) () -> ()
%3 = begin_access [modify] [dynamic] %0
%4 = apply %2() : $@convention(thin) () -> ()
end_access %3
%6 = begin_access [read] [dynamic] %0
%7 = apply %2() : $@convention(thin) () -> ()
end_access %6
%r = tuple ()
return %r
}
// CHECK-LABEL: sil [ossa] @unknown_call_via_destructor :
// CHECK: %2 = begin_access [read]
// CHECK: end_access %2
// CHECK: } // end sil function 'unknown_call_via_destructor'
sil [ossa] @unknown_call_via_destructor : $@convention(thin) (@owned AnyObject) -> () {
bb0(%0: @owned $AnyObject):
%1 = global_addr @g1 : $*Int
%2 = begin_access [read] [dynamic] %1
destroy_value %0
end_access %2
%r = tuple ()
return %r
}
// CHECK-LABEL: sil [ossa] @two_inner_accesses :
// CHECK: %1 = begin_access [read]
// CHECK-NEXT: %2 = begin_access [modify]
// CHECK-NEXT: end_access %2
// CHECK-NEXT: end_access %1
// CHECK: } // end sil function 'two_inner_accesses'
sil [ossa] @two_inner_accesses : $@convention(thin) () -> () {
bb0:
%0 = global_addr @g1 : $*Int
%1 = begin_access [read] [dynamic] %0
%2 = begin_access [modify] [dynamic] %0
end_access %2
%4 = begin_access [read] [dynamic] %0
end_access %4
end_access %1
%r = tuple ()
return %r
}
// CHECK-LABEL: sil [ossa] @partially_overlapping_accesses :
// CHECK: %2 = begin_access [read] [dynamic] %1
// CHECK: apply
// CHECK-NEXT: end_access %2
// CHECK-NEXT: tuple
// CHECK: } // end sil function 'partially_overlapping_accesses'
sil [ossa] @partially_overlapping_accesses : $@convention(thin) () -> () {
bb0:
%0 = global_addr @g1 : $*Int
%1 = global_addr @g2 : $*Int
%2 = begin_access [read] [dynamic] %0
%3 = begin_access [read] [dynamic] %1
end_access %2
%4 = function_ref @unknown : $@convention(thin) () -> ()
%5 = apply %4() : $@convention(thin) () -> ()
%6 = begin_access [read] [dynamic] %0
end_access %3
end_access %6
%r = tuple ()
return %r
}
// CHECK-LABEL: sil [ossa] @multi_block1 :
// CHECK: bb0:
// CHECK: %1 = begin_access
// CHECK: bb1:
// CHECK: %3 = begin_access
// CHECK: bb2:
// CHECK: %6 = begin_access
// CHECK: bb3:
// CHECK: %9 = begin_access
// CHECK: } // end sil function 'multi_block1'
sil [ossa] @multi_block1 : $@convention(thin) () -> () {
bb0:
%0 = global_addr @g1 : $*Int
%1 = begin_access [modify] [dynamic] %0
cond_br undef, bb1, bb2
bb1:
%3 = begin_access [read] [dynamic] %0
end_access %3
br bb3
bb2:
%6 = begin_access [read] [dynamic] %0
end_access %6
br bb3
bb3:
%9 = begin_access [read] [dynamic] %0
end_access %9
end_access %1
%r = tuple ()
return %r
}
// CHECK-LABEL: sil [ossa] @multi_block2 :
// CHECK: bb0:
// CHECK: %1 = begin_access
// CHECK: bb1:
// CHECK: %3 = begin_access
// CHECK: bb2:
// CHECK: end_access %1
// CHECK-NEXT: br bb3
// CHECK: bb3:
// CHECK-NEXT: tuple
// CHECK: } // end sil function 'multi_block2'
sil [ossa] @multi_block2 : $@convention(thin) () -> () {
bb0:
%0 = global_addr @g1 : $*Int
%1 = begin_access [modify] [dynamic] %0
cond_br undef, bb1, bb2
bb1:
%3 = begin_access [read] [dynamic] %0
end_access %3
end_access %1
br bb3
bb2:
end_access %1
%8 = begin_access [read] [dynamic] %0
end_access %8
br bb3
bb3:
%11 = begin_access [read] [dynamic] %0
end_access %11
%r = tuple ()
return %r
}
// CHECK-LABEL: sil [ossa] @nested_static :
// CHECK-NOT: begin_access [modify] [static]
// CHECK: } // end sil function 'nested_static'
sil [ossa] @nested_static : $@convention(thin) () -> () {
bb0:
%0 = global_addr @g1 : $*Int
%1 = begin_access [modify] [dynamic] %0
%2 = begin_access [modify] [static] %1
end_access %2
end_access %1
%r = tuple ()
return %r
}
// CHECK-LABEL: sil [ossa] @non_dead_inside_dead :
// CHECK: begin_access [modify]
// CHECK: begin_access [read]
// CHECK: } // end sil function 'non_dead_inside_dead'
sil [ossa] @non_dead_inside_dead : $@convention(thin) () -> Int {
bb0:
%0 = global_addr @g1 : $*Int
%1 = begin_access [modify] [dynamic] %0
%2 = begin_access [read] [dynamic] %0
%3 = load [trivial] %2
end_access %2
end_access %1
return %3
}