Skip to content

Commit 9b66d7c

Browse files
authored
Merge pull request #82603 from atrick/62-local-switch
[6.2] Fix LocalVariableUtils switch_enum_addr.
2 parents 44a43df + 43193b6 commit 9b66d7c

File tree

5 files changed

+159
-4
lines changed

5 files changed

+159
-4
lines changed

SwiftCompilerSources/Sources/Optimizer/FunctionPasses/LifetimeDependenceScopeFixup.swift

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -650,7 +650,10 @@ extension ScopeExtension {
650650
return .continueWalk
651651
}
652652
defer {walker.deinitialize()}
653-
_ = walker.walkDown(dependence: dependence)
653+
// walkDown may abort if any utility used by address use walker, such asLocalVarUtils, has unhandled cases.
654+
if walker.walkDown(dependence: dependence) == .abortWalk {
655+
return nil
656+
}
654657
dependsOnCaller = walker.dependsOnCaller
655658
}
656659
for owner in owners {

SwiftCompilerSources/Sources/Optimizer/Utilities/LifetimeDependenceUtils.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -936,6 +936,10 @@ extension LifetimeDependenceDefUseWalker {
936936
return loadedAddressUse(of: localAccess.operand!, intoValue: load)
937937
case let copyAddr as SourceDestAddrInstruction:
938938
return loadedAddressUse(of: localAccess.operand!, intoAddress: copyAddr.destinationOperand)
939+
case is SwitchEnumAddrInst:
940+
// switch_enum_addr does not produce any values. Subsequent uses of the address (unchecked_enum_data_addr)
941+
// directly use the original address.
942+
return .continueWalk
939943
default:
940944
return .abortWalk
941945
}

SwiftCompilerSources/Sources/Optimizer/Utilities/LocalVariableUtils.swift

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -468,13 +468,18 @@ extension LocalVariableAccessWalker: AddressUseVisitor {
468468
mutating func leafAddressUse(of operand: Operand) -> WalkResult {
469469
switch operand.instruction {
470470
case is StoringInstruction, is SourceDestAddrInstruction, is DestroyAddrInst, is DeinitExistentialAddrInst,
471-
is InjectEnumAddrInst, is SwitchEnumAddrInst, is TupleAddrConstructorInst, is InitBlockStorageHeaderInst,
472-
is PackElementSetInst:
473-
// Handle instructions that initialize both temporaries and local variables.
471+
is InjectEnumAddrInst, is TupleAddrConstructorInst, is InitBlockStorageHeaderInst, is PackElementSetInst:
472+
// Handle instructions that initialize both temporaries and local variables. If operand's address is the same as
473+
// the local variable's address, then this fully kills operand liveness. The original value in operand's address
474+
// cannot be used in any way.
474475
visit(LocalVariableAccess(.store, operand))
475476
case let md as MarkDependenceAddrInst:
476477
assert(operand == md.addressOperand)
477478
visit(LocalVariableAccess(.dependenceDest, operand))
479+
case is SwitchEnumAddrInst:
480+
// switch_enum_addr is truly a leaf address use. It does not produce a new value. But in every other respect it is
481+
// like a load.
482+
visit(LocalVariableAccess(.load, operand))
478483
case is DeallocStackInst:
479484
break
480485
default:

test/SILOptimizer/lifetime_dependence/scope_fixup.sil

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,26 @@ struct Holder {
6767
@_addressableForDependencies
6868
struct AddressableForDeps {}
6969

70+
public struct MutableView : ~Copyable, ~Escapable {
71+
@_hasStorage var ptr: UnsafeMutableRawBufferPointer { get set }
72+
init(ptr: UnsafeMutableRawBufferPointer)
73+
}
74+
75+
public struct Container : ~Escapable, ~Copyable {
76+
@_hasStorage var somethings: UnsafeMutableRawBufferPointer { get set }
77+
init(somethings: UnsafeMutableRawBufferPointer)
78+
}
79+
80+
public struct AThing : ~Copyable {
81+
@_hasStorage var somethings: UnsafeMutableRawBufferPointer { get set }
82+
init(somethings: UnsafeMutableRawBufferPointer)
83+
}
84+
85+
sil @getContainer : $@convention(thin) (@inout AThing) -> @lifetime(borrow address_for_deps 0) @out Container
86+
sil @getMutableView : $@convention(thin) (@in_guaranteed Container) -> @lifetime(copy 0) @out Optional<MutableView>
87+
sil @modAThing : $@convention(thin) (@inout AThing) -> ()
88+
sil @getSpan : $@convention(thin) (@inout MutableView) -> @lifetime(borrow address_for_deps 0) @owned MutableRawSpan
89+
7090
sil @getNEPointerToA : $@convention(thin) (@guaranteed NE) -> UnsafePointer<A>
7191
sil @useA : $@convention(thin) (A) -> ()
7292

@@ -679,3 +699,95 @@ bb2(%25 : @guaranteed $NCWrapper):
679699
%37 = tuple ()
680700
return %37
681701
}
702+
703+
// rdar://151231236 ([~Escapable] Missing 'overlapping acceses' error when called from client code, but exact same code
704+
// produces error in same module)
705+
//
706+
// Test access scope expansion over a switch_enum_addr
707+
// CHECK-LABEL: sil hidden [ossa] @testSwitchEnumAddr : $@convention(thin) @async (@inout AThing) -> @error any Error {
708+
// CHECK: bb0(%0 : $*AThing):
709+
// CHECK: [[ATHING:%[0-9]+]] = mark_unresolved_non_copyable_value [consumable_and_assignable] %0
710+
// CHECK: [[ACCESS:%[0-9]+]] = begin_access [modify] [unknown] [[ATHING]]
711+
// CHECK: apply {{.*}}(%{{.*}}, [[ACCESS]]) : $@convention(thin) (@inout AThing) -> @lifetime(borrow address_for_deps 0) @out Container
712+
// CHECK: mark_dependence_addr [unresolved] %{{.*}} on [[ACCESS]]
713+
// CHECK: [[VIEW:%[0-9]+]] = project_box
714+
// CHECK: [[TMP:%[0-9]+]] = alloc_stack $Optional<MutableView>
715+
// CHECK: [[TMP_NC:%[0-9]+]] = mark_unresolved_non_copyable_value [consumable_and_assignable] [[TMP]]
716+
// CHECK: apply %{{.*}} : $@convention(thin) (@in_guaranteed Container) -> @lifetime(copy 0) @out Optional<MutableView>
717+
// CHECK: switch_enum_addr [[TMP_NC]], case #Optional.some!enumelt: bb2, case #Optional.none!enumelt: bb1
718+
// CHECK: bb1:
719+
// CHECK: bb2:
720+
// CHECK: [[DATA:%[0-9]+]] = unchecked_take_enum_data_addr [[TMP_NC]], #Optional.some!enumelt
721+
// CHECK: copy_addr [take] [[DATA]] to [init] [[VIEW]]
722+
// CHECK: begin_access [modify] [unknown] [[ATHING]]
723+
// CHECK: apply %{{.*}} : $@convention(thin) (@inout AThing) -> ()
724+
// CHECK: [[ACCESS_VIEW:%[0-9]+]] = begin_access [modify] [unknown] [[VIEW]]
725+
// CHECK: apply {{.*}} : $@convention(thin) (@inout MutableView) -> @lifetime(borrow address_for_deps 0) @owned MutableRawSpan
726+
// CHECK: mark_dependence [unresolved]
727+
// CHECK: end_access [[ACCESS_VIEW]]
728+
// CHECK: end_access [[ACCESS]]
729+
// CHECK: store
730+
// CHECK-LABEL: } // end sil function 'testSwitchEnumAddr'
731+
sil hidden [ossa] @testSwitchEnumAddr : $@convention(thin) @async (@inout AThing) -> @error any Error {
732+
bb0(%0 : $*AThing):
733+
%1 = mark_unresolved_non_copyable_value [consumable_and_assignable] %0
734+
%2 = alloc_box ${ var Container }, var, name "container"
735+
%3 = begin_borrow [lexical] [var_decl] %2
736+
%4 = project_box %3, 0
737+
%5 = begin_access [modify] [unknown] %1
738+
739+
%6 = function_ref @getContainer : $@convention(thin) (@inout AThing) -> @lifetime(borrow address_for_deps 0) @out Container
740+
%7 = apply %6(%4, %5) : $@convention(thin) (@inout AThing) -> @lifetime(borrow address_for_deps 0) @out Container
741+
mark_dependence_addr [unresolved] %4 on %5
742+
end_access %5
743+
%10 = alloc_box ${ var MutableView }, var, name "view"
744+
%11 = begin_borrow [lexical] [var_decl] %10
745+
%12 = project_box %11, 0
746+
747+
%13 = alloc_stack $Optional<MutableView>
748+
%14 = mark_unresolved_non_copyable_value [consumable_and_assignable] %13
749+
%15 = begin_access [read] [unknown] %4
750+
%16 = mark_unresolved_non_copyable_value [no_consume_or_assign] %15
751+
752+
%17 = function_ref @getMutableView : $@convention(thin) (@in_guaranteed Container) -> @lifetime(copy 0) @out Optional<MutableView>
753+
%18 = apply %17(%14, %16) : $@convention(thin) (@in_guaranteed Container) -> @lifetime(copy 0) @out Optional<MutableView>
754+
end_access %15
755+
756+
switch_enum_addr %14, case #Optional.some!enumelt: bb2, case #Optional.none!enumelt: bb1
757+
758+
bb1:
759+
end_borrow %11
760+
dealloc_box [dead_end] %10
761+
end_borrow %3
762+
dealloc_box [dead_end] %2
763+
unreachable
764+
765+
bb2:
766+
%44 = unchecked_take_enum_data_addr %14, #Optional.some!enumelt
767+
copy_addr [take] %44 to [init] %12
768+
dealloc_stack %13
769+
770+
%22 = begin_access [modify] [unknown] %1
771+
%23 = function_ref @modAThing : $@convention(thin) (@inout AThing) -> ()
772+
%24 = apply %23(%22) : $@convention(thin) (@inout AThing) -> ()
773+
end_access %22
774+
%26 = alloc_box ${ var MutableRawSpan }, var, name "span"
775+
%27 = begin_borrow [lexical] [var_decl] %26
776+
%28 = project_box %27, 0
777+
%29 = begin_access [modify] [unknown] %12
778+
%30 = mark_unresolved_non_copyable_value [assignable_but_not_consumable] %29
779+
780+
%31 = function_ref @getSpan : $@convention(thin) (@inout MutableView) -> @lifetime(borrow address_for_deps 0) @owned MutableRawSpan
781+
%32 = apply %31(%30) : $@convention(thin) (@inout MutableView) -> @lifetime(borrow address_for_deps 0) @owned MutableRawSpan
782+
%33 = mark_dependence [unresolved] %32 on %30
783+
end_access %29
784+
store %33 to [init] %28
785+
end_borrow %27
786+
destroy_value %26
787+
end_borrow %11
788+
destroy_value %10
789+
end_borrow %3
790+
destroy_value %2
791+
%42 = tuple ()
792+
return %42
793+
}

test/SILOptimizer/lifetime_dependence/verify_diagnostics.swift

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -243,3 +243,34 @@ func testVoid() -> NEInt {
243243
let ne = NEInt(immortal: 3)
244244
return _overrideLifetime(ne, borrowing: ())
245245
}
246+
247+
// =============================================================================
248+
// Optional
249+
// =============================================================================
250+
251+
// A view that contains some arbitrary opaque type, making it address-only, but also depends on an unsafe pointer.
252+
public struct AddressOnlyMutableView<T> : ~Copyable, ~Escapable {
253+
let t: T
254+
255+
// placeholder
256+
@_lifetime(borrow holder)
257+
init(holder: borrowing Holder, t: T) { self.t = t }
258+
259+
mutating func modify() {}
260+
}
261+
262+
// Return an address-only optional view (requiring switch_enum_addr).
263+
@_silgen_name("addressOnlyMutableView")
264+
@_lifetime(&holder)
265+
func addressOnlyMutableView<T>(holder: inout Holder, with t: T) -> AddressOnlyMutableView<T>?
266+
267+
// rdar://151231236 ([~Escapable] Missing 'overlapping acceses' error when called from client code, but exact same code
268+
// produces error in same module)
269+
//
270+
// Extend the access scope for &holder across the switch_enum_addr required to unwrap Optional<AddressOnlyMutableView>.
271+
func testSwitchAddr<T>(holder: inout Holder, t: T) {
272+
// mutableView must be a 'var' to require local variable data flow.
273+
var mutableView = addressOnlyMutableView(holder: &holder, with: t)! // expected-error {{overlapping accesses to 'holder', but modification requires exclusive access; consider copying to a local variable}}
274+
mutate(&holder) // expected-note {{conflicting access is here}}
275+
mutableView.modify()
276+
}

0 commit comments

Comments
 (0)