Skip to content

WalkUtils: handle unsafe_ref_cast which casts between AnyObject and a class #75434

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jul 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions SwiftCompilerSources/Sources/SIL/Type.swift
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ public struct Type : CustomStringConvertible, NoReflectionChildren {
public var isEnum: Bool { bridged.isEnumOrBoundGenericEnum() }
public var isFunction: Bool { bridged.isFunction() }
public var isMetatype: Bool { bridged.isMetatype() }
public var isClassExistential: Bool { bridged.isClassExistential() }
public var isNoEscapeFunction: Bool { bridged.isNoEscapeFunction() }
public var containsNoEscapeFunction: Bool { bridged.containsNoEscapeFunction() }
public var isThickFunction: Bool { bridged.isThickFunction() }
Expand Down
20 changes: 18 additions & 2 deletions SwiftCompilerSources/Sources/SIL/Utilities/WalkUtils.swift
Original file line number Diff line number Diff line change
Expand Up @@ -360,9 +360,17 @@ extension ValueDefUseWalker {
return unmatchedPath(value: operand, path: path)
}
case is BeginBorrowInst, is CopyValueInst, is MoveValueInst,
is UpcastInst, is UncheckedRefCastInst, is EndCOWMutationInst, is EndInitLetRefInst,
is UpcastInst, is EndCOWMutationInst, is EndInitLetRefInst,
is RefToBridgeObjectInst, is BridgeObjectToRefInst, is MarkUnresolvedNonCopyableValueInst:
return walkDownUses(ofValue: (instruction as! SingleValueInstruction), path: path)
case let urc as UncheckedRefCastInst:
if urc.type.isClassExistential || urc.fromInstance.type.isClassExistential {
// Sometimes `unchecked_ref_cast` is misused to cast between AnyObject and a class (instead of
// init_existential_ref and open_existential_ref).
// We need to ignore this because otherwise the path wouldn't contain the right `existential` field kind.
return leafUse(value: operand, path: path)
}
return walkDownUses(ofValue: urc, path: path)
case let beginDealloc as BeginDeallocRefInst:
if operand.index == 0 {
return walkDownUses(ofValue: beginDealloc, path: path)
Expand Down Expand Up @@ -680,10 +688,18 @@ extension ValueUseDefWalker {
case let oer as OpenExistentialRefInst:
return walkUp(value: oer.existential, path: path.push(.existential, index: 0))
case is BeginBorrowInst, is CopyValueInst, is MoveValueInst,
is UpcastInst, is UncheckedRefCastInst, is EndCOWMutationInst, is EndInitLetRefInst,
is UpcastInst, is EndCOWMutationInst, is EndInitLetRefInst,
is BeginDeallocRefInst,
is RefToBridgeObjectInst, is BridgeObjectToRefInst, is MarkUnresolvedNonCopyableValueInst:
return walkUp(value: (def as! Instruction).operands[0].value, path: path)
case let urc as UncheckedRefCastInst:
if urc.type.isClassExistential || urc.fromInstance.type.isClassExistential {
// Sometimes `unchecked_ref_cast` is misused to cast between AnyObject and a class (instead of
// init_existential_ref and open_existential_ref).
// We need to ignore this because otherwise the path wouldn't contain the right `existential` field kind.
return rootDef(value: urc, path: path)
}
return walkUp(value: urc.fromInstance, path: path)
case let arg as Argument:
if let phi = Phi(arg) {
for incoming in phi.incomingValues {
Expand Down
1 change: 1 addition & 0 deletions include/swift/SIL/SILBridging.h
Original file line number Diff line number Diff line change
Expand Up @@ -400,6 +400,7 @@ struct BridgedType {
BRIDGED_INLINE bool isEnumOrBoundGenericEnum() const;
BRIDGED_INLINE bool isFunction() const;
BRIDGED_INLINE bool isMetatype() const;
BRIDGED_INLINE bool isClassExistential() const;
BRIDGED_INLINE bool isNoEscapeFunction() const;
BRIDGED_INLINE bool containsNoEscapeFunction() const;
BRIDGED_INLINE bool isThickFunction() const;
Expand Down
4 changes: 4 additions & 0 deletions include/swift/SIL/SILBridgingImpl.h
Original file line number Diff line number Diff line change
Expand Up @@ -296,6 +296,10 @@ bool BridgedType::isMetatype() const {
return unbridged().isMetatype();
}

bool BridgedType::isClassExistential() const {
return unbridged().isClassExistentialType();
}

bool BridgedType::isNoEscapeFunction() const {
return unbridged().isNoEscapeFunction();
}
Expand Down
41 changes: 41 additions & 0 deletions test/SILOptimizer/addr_escape_info.sil
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ class X {
@_hasStorage var s: Str
}

class D: X {}

struct Container {
@_hasStorage var x: X
}
Expand Down Expand Up @@ -755,3 +757,42 @@ bb0:
return %8 : $()
}

// CHECK-LABEL: Address escape information for test_unchecked_ref_cast:
// CHECK: pair 0 - 1
// CHECK-NEXT: %4 = ref_element_addr %2 : $X, #X.s
// CHECK-NEXT: %6 = ref_element_addr %3 : $X, #X.s
// CHECK-NEXT: no alias
// CHECK: End function test_unchecked_ref_cast
sil @test_unchecked_ref_cast : $@convention(thin) () -> () {
bb0:
%0 = alloc_ref $D
%1 = alloc_ref $D
%2 = unchecked_ref_cast %0 : $D to $X
%3 = unchecked_ref_cast %1 : $D to $X
%4 = ref_element_addr %2 : $X, #X.s
fix_lifetime %4 : $*Str
%6 = ref_element_addr %3 : $X, #X.s
fix_lifetime %6 : $*Str
%r = tuple ()
return %r : $()
}

// CHECK-LABEL: Address escape information for test_anyobject_cast:
// CHECK: pair 0 - 1
// CHECK-NEXT: %3 = ref_element_addr %2 : $X, #X.s
// CHECK-NEXT: %5 = ref_element_addr %2 : $X, #X.s
// CHECK-NEXT: may alias
// CHECK: End function test_anyobject_cast
sil @test_anyobject_cast : $@convention(thin) () -> () {
bb0:
%0 = alloc_ref $X
%1 = init_existential_ref %0 : $X : $X, $AnyObject
%2 = unchecked_ref_cast %1 : $AnyObject to $X
%3 = ref_element_addr %2 : $X, #X.s
fix_lifetime %3 : $*Str
%5 = ref_element_addr %2 : $X, #X.s
fix_lifetime %5 : $*Str
%r = tuple ()
return %r : $()
}

12 changes: 12 additions & 0 deletions test/SILOptimizer/escape_info.sil
Original file line number Diff line number Diff line change
Expand Up @@ -1445,3 +1445,15 @@ bb0(%0 : $F):
return %r : $()
}

// CHECK-LABEL: Escape information for test_unchecked_ref_cast:
// CHECK: return[]: %0 = alloc_ref $Derived
// CHECK: - : %1 = alloc_ref $Derived
// CHECK: End function test_unchecked_ref_cast
sil @test_unchecked_ref_cast : $@convention(thin) () -> @owned X {
bb0:
%0 = alloc_ref $Derived
%1 = alloc_ref $Derived
%2 = unchecked_ref_cast %0 : $Derived to $X
%3 = unchecked_ref_cast %1 : $Derived to $X
return %2 : $X
}
26 changes: 26 additions & 0 deletions test/SILOptimizer/redundant_load_elim.sil
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,11 @@ final class NewHalfOpenRangeGenerator : NewRangeGenerator1 {
override init(start: Int32, end: Int32)
}

class COpt {
final var i: Optional<Int32>
init()
}

sil_global @total : $Int32

sil @use : $@convention(thin) (Builtin.Int32) -> ()
Expand Down Expand Up @@ -1315,3 +1320,24 @@ bb0(%0 : $Int):
dealloc_stack %1 : $*Int
return %53 : $Int
}

// CHECK-LABEL: sil [ossa] @test_anyobject_cast :
// CHECK: store
// CHECK: [[L:%[0-9]+]] = load
// CHECK: return [[L]]
// CHECK-LABEL: } // end sil function 'test_anyobject_cast'
sil [ossa] @test_anyobject_cast : $@convention(thin) (@guaranteed COpt, Int32) -> Optional<Int32> {
bb0(%0 : @guaranteed $COpt, %1 : $Int32):
%2 = init_existential_ref %0 : $COpt : $COpt, $AnyObject
%3 = unchecked_ref_cast %2 : $AnyObject to $COpt
%4 = ref_element_addr %3 : $COpt, #COpt.i
%5 = load [trivial] %4 : $*Optional<Int32>
%6 = ref_element_addr %3 : $COpt, #COpt.i
%7 = init_enum_data_addr %6 : $*Optional<Int32>, #Optional.some!enumelt
store %1 to [trivial] %7 : $*Int32
%9 = ref_element_addr %3 : $COpt, #COpt.i
%10 = load [trivial] %9 : $*Optional<Int32>
return %10 : $Optional<Int32>
}