Merge pull request #75434 from eeckstein/fix-walkutils

WalkUtils: handle `unsafe_ref_cast` which casts between AnyObject and a class
This commit is contained in:
eeckstein
2024-07-24 17:51:18 +02:00
committed by GitHub
7 changed files with 103 additions and 2 deletions

View File

@@ -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() }

View File

@@ -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)
@@ -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 {

View File

@@ -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;

View File

@@ -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();
}

View File

@@ -17,6 +17,8 @@ class X {
@_hasStorage var s: Str
}
class D: X {}
struct Container {
@_hasStorage var x: X
}
@@ -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 : $()
}

View File

@@ -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
}

View File

@@ -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) -> ()
@@ -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>
}