Skip to content

Commit f9abb02

Browse files
authored
Merge pull request #66763 from eeckstein/existential-ref-cast-opt
Optimizer: add simplifications for checked_cast_br and unchecked_ref_cast
2 parents dd28827 + 9474b9d commit f9abb02

File tree

8 files changed

+347
-1
lines changed

8 files changed

+347
-1
lines changed

SwiftCompilerSources/Sources/Optimizer/InstructionSimplification/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ swift_compiler_sources(Optimizer
1919
SimplifyInitEnumDataAddr.swift
2020
SimplifyLoad.swift
2121
SimplifyPartialApply.swift
22+
SimplifyRefCasts.swift
2223
SimplifyRetainReleaseValue.swift
2324
SimplifyStrongRetainRelease.swift
2425
SimplifyStructExtract.swift
Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
//===--- SimplifyRefCasts.swift -------------------------------------------===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2014 - 2023 Apple Inc. and the Swift project authors
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See https://swift.org/LICENSE.txt for license information
9+
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
10+
//
11+
//===----------------------------------------------------------------------===//
12+
13+
import SIL
14+
15+
// Note: this simplifications are not SILCombineSimplifyable, because SILCombine has
16+
// its own simplifications for those cast instructions which are not ported to Swift, yet.
17+
18+
extension CheckedCastBranchInst : OnoneSimplifyable {
19+
func simplify(_ context: SimplifyContext) {
20+
// Has only an effect if the source is an (existential) reference.
21+
simplifySourceOperandOfRefCast(context)
22+
}
23+
}
24+
25+
extension UncheckedRefCastInst : OnoneSimplifyable {
26+
func simplify(_ context: SimplifyContext) {
27+
simplifySourceOperandOfRefCast(context)
28+
}
29+
}
30+
31+
private extension UnaryInstruction {
32+
33+
/// Look through `upcast` and `init_existential_ref` instructions and replace the
34+
/// operand of this cast instruction with the original value.
35+
/// For example:
36+
/// ```
37+
/// %2 = upcast %1 : $Derived to $Base
38+
/// %3 = init_existential_ref %2 : $Base : $Base, $AnyObject
39+
/// checked_cast_br %3 : $AnyObject to Derived, bb1, bb2
40+
/// ```
41+
///
42+
/// This makes it more likely that the cast can be constant folded because the source
43+
/// operand's type is more accurate. In the example above, the cast reduces to
44+
/// ```
45+
/// checked_cast_br %1 : $Derived to Derived, bb1, bb2
46+
/// ```
47+
/// which can be trivially folded to always-succeeds.
48+
///
49+
func simplifySourceOperandOfRefCast(_ context: SimplifyContext) {
50+
while true {
51+
switch operand.value {
52+
case let ier as InitExistentialRefInst:
53+
if !tryReplaceSource(withOperandOf: ier, context) {
54+
return
55+
}
56+
case let uc as UpcastInst:
57+
if !tryReplaceSource(withOperandOf: uc, context) {
58+
return
59+
}
60+
default:
61+
return
62+
}
63+
}
64+
65+
}
66+
67+
func tryReplaceSource(withOperandOf inst: SingleValueInstruction, _ context: SimplifyContext) -> Bool {
68+
let singleUse = context.preserveDebugInfo ? inst.uses.singleUse : inst.uses.singleNonDebugUse
69+
let canEraseInst = singleUse?.instruction == self
70+
let replacement = inst.operands[0].value
71+
72+
if parentFunction.hasOwnership {
73+
if !canEraseInst && replacement.ownership == .owned {
74+
// We cannot add more uses to `replacement` without inserting a copy.
75+
return false
76+
}
77+
78+
operand.set(to: replacement, context)
79+
80+
if let ccb = self as? CheckedCastBranchInst {
81+
// In OSSA, the source value is passed as block argument to the failure block.
82+
// We have to re-create the skipped source instruction in the failure block.
83+
insertCompensatingInstructions(for: inst, in: ccb.failureBlock, context)
84+
}
85+
} else {
86+
operand.set(to: replacement, context)
87+
}
88+
89+
if canEraseInst {
90+
context.erase(instructionIncludingDebugUses: inst)
91+
}
92+
return true
93+
}
94+
}
95+
96+
/// Compensate a removed source value instruction in the failure block.
97+
/// For example:
98+
/// ```
99+
/// %inst = upcast %sourceValue : $Derived to $Base
100+
/// checked_cast_br %inst : $Base to Derived, success_block, failure_block
101+
/// ...
102+
/// failure_block(%oldArg : $Base):
103+
/// ```
104+
/// is converted to:
105+
/// ```
106+
/// checked_cast_br %sourceValue : $Derived to Derived, success_block, failure_block
107+
/// ...
108+
/// failure_block(%newArg : $Derived):
109+
/// %3 = upcast %newArg : $Derived to $Base
110+
/// ```
111+
private func insertCompensatingInstructions(for inst: Instruction, in failureBlock: BasicBlock, _ context: SimplifyContext) {
112+
assert(failureBlock.arguments.count == 1)
113+
let sourceValue = inst.operands[0].value
114+
let newArg = failureBlock.addBlockArgument(type: sourceValue.type, ownership: sourceValue.ownership, context)
115+
let builder = Builder(atBeginOf: failureBlock, context)
116+
let newInst: SingleValueInstruction
117+
switch inst {
118+
case let ier as InitExistentialRefInst:
119+
newInst = builder.createInitExistentialRef(instance: newArg, existentialType: ier.type, useConformancesOf: ier)
120+
case let uc as UpcastInst:
121+
newInst = builder.createUpcast(from: newArg, to: uc.type)
122+
default:
123+
fatalError("unhandled instruction")
124+
}
125+
let oldArg = failureBlock.arguments[0]
126+
oldArg.uses.replaceAll(with: newInst, context)
127+
failureBlock.eraseArgument(at: 0, context)
128+
}

SwiftCompilerSources/Sources/SIL/Builder.swift

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -262,4 +262,13 @@ public struct Builder {
262262
let store = bridged.createStore(source.bridged, destination.bridged, ownership.rawValue)
263263
return notifyNew(store.getAs(StoreInst.self))
264264
}
265+
266+
public func createInitExistentialRef(instance: Value,
267+
existentialType: Type,
268+
useConformancesOf: InitExistentialRefInst) -> InitExistentialRefInst {
269+
let initExistential = bridged.createInitExistentialRef(instance.bridged,
270+
existentialType.bridged,
271+
useConformancesOf.bridged)
272+
return notifyNew(initExistential.getAs(InitExistentialRefInst.self))
273+
}
265274
}

SwiftCompilerSources/Sources/SIL/Instruction.swift

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1004,6 +1004,9 @@ final public class AwaitAsyncContinuationInst : TermInst, UnaryInstruction {
10041004
}
10051005

10061006
final public class CheckedCastBranchInst : TermInst, UnaryInstruction {
1007+
public var source: Value { operand.value }
1008+
public var successBlock: BasicBlock { bridged.CheckedCastBranch_getSuccessBlock().block }
1009+
public var failureBlock: BasicBlock { bridged.CheckedCastBranch_getFailureBlock().block }
10071010
}
10081011

10091012
final public class CheckedCastAddrBranchInst : TermInst, UnaryInstruction {

include/swift/SIL/SILBridging.h

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -786,6 +786,12 @@ struct BridgedInstruction {
786786
}, [](swift::SILDeclRef) {});
787787
}
788788

789+
SWIFT_IMPORT_UNSAFE
790+
inline BridgedBasicBlock CheckedCastBranch_getSuccessBlock() const;
791+
792+
SWIFT_IMPORT_UNSAFE
793+
inline BridgedBasicBlock CheckedCastBranch_getFailureBlock() const;
794+
789795
SWIFT_IMPORT_UNSAFE
790796
swift::SubstitutionMap ApplySite_getSubstitutionMap() const {
791797
auto as = swift::ApplySite(getInst());
@@ -1258,6 +1264,17 @@ struct BridgedBuilder{
12581264
return {builder().createStore(regularLoc(), src.getSILValue(), dst.getSILValue(),
12591265
(swift::StoreOwnershipQualifier)ownership)};
12601266
}
1267+
1268+
SWIFT_IMPORT_UNSAFE
1269+
BridgedInstruction createInitExistentialRef(BridgedValue instance,
1270+
swift::SILType type,
1271+
BridgedInstruction useConformancesOf) const {
1272+
auto *src = useConformancesOf.getAs<swift::InitExistentialRefInst>();
1273+
return {builder().createInitExistentialRef(regularLoc(), type,
1274+
src->getFormalConcreteType(),
1275+
instance.getSILValue(),
1276+
src->getConformances())};
1277+
}
12611278
};
12621279

12631280
// AST bridging
@@ -1352,6 +1369,14 @@ void BridgedInstruction::TermInst_replaceBranchTarget(BridgedBasicBlock from, Br
13521369
getAs<swift::TermInst>()->replaceBranchTarget(from.getBlock(), to.getBlock());
13531370
}
13541371

1372+
BridgedBasicBlock BridgedInstruction::CheckedCastBranch_getSuccessBlock() const {
1373+
return {getAs<swift::CheckedCastBranchInst>()->getSuccessBB()};
1374+
}
1375+
1376+
inline BridgedBasicBlock BridgedInstruction::CheckedCastBranch_getFailureBlock() const {
1377+
return {getAs<swift::CheckedCastBranchInst>()->getFailureBB()};
1378+
}
1379+
13551380
OptionalBridgedSuccessor BridgedBasicBlock::getFirstPred() const {
13561381
return {getBlock()->pred_begin().getSuccessorRef()};
13571382
}

test/SILOptimizer/definite_init_failable_initializers_objc.swift

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
// long enough in the same form to be worth rewriting CHECK lines.
66

77
// REQUIRES: objc_interop
8+
// REQUIRES: swift_in_compiler
89

910
import ObjectiveC
1011

@@ -68,7 +69,7 @@ class Cat : FakeNSObject {
6869
// CHECK-NEXT: strong_release [[ARG2]]
6970
// CHECK-NEXT: [[RELOAD_ARG2:%.*]] = load [[SELF_BOX]]
7071
// CHECK-NEXT: [[SUPER:%.*]] = upcast [[RELOAD_ARG2]] : $Cat to $FakeNSObject
71-
// CHECK-NEXT: [[SUB:%.*]] = unchecked_ref_cast [[SUPER]] : $FakeNSObject to $Cat
72+
// CHECK-NEXT: [[SUB:%.*]] = unchecked_ref_cast [[RELOAD_ARG2]] : $Cat to $Cat
7273
// CHECK-NEXT: [[SUPER_FN:%.*]] = objc_super_method [[SUB]] : $Cat, #FakeNSObject.init!initializer.foreign : (FakeNSObject.Type) -> () -> FakeNSObject, $@convention(objc_method) (@owned FakeNSObject) -> @owned FakeNSObject
7374
// CHECK-NEXT: [[NEW_SUPER_SELF:%.*]] = apply [[SUPER_FN]]([[SUPER]]) : $@convention(objc_method) (@owned FakeNSObject) -> @owned FakeNSObject
7475
// CHECK-NEXT: [[NEW_SELF:%.*]] = unchecked_ref_cast [[NEW_SUPER_SELF]] : $FakeNSObject to $Cat
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
// RUN: %target-sil-opt -enable-sil-verify-all %s -onone-simplification -simplify-instruction=checked_cast_br | %FileCheck %s --check-prefix=CHECK --check-prefix=CHECK-ONONE
2+
// RUN: %target-sil-opt -enable-sil-verify-all %s -simplification -simplify-instruction=checked_cast_br | %FileCheck %s --check-prefix=CHECK --check-prefix=CHECK-O
3+
4+
// REQUIRES: swift_in_compiler
5+
6+
import Swift
7+
import Builtin
8+
9+
protocol P : AnyObject {}
10+
class B : P {}
11+
class X : B {}
12+
13+
// CHECK-LABEL: sil @test_non_ossa :
14+
// CHECK: %0 = alloc_ref
15+
// CHECK: checked_cast_br %0 : $X
16+
// CHECK: } // end sil function 'test_non_ossa'
17+
sil @test_non_ossa : $@convention(thin) () -> AnyObject {
18+
bb0:
19+
%0 = alloc_ref $X
20+
%1 = upcast %0 : $X to $B
21+
%2 = init_existential_ref %1 : $B : $B, $AnyObject
22+
checked_cast_br %2 : $AnyObject to X, bb1, bb2
23+
24+
bb1(%5 : $X):
25+
strong_release %5 : $X
26+
return %2 : $AnyObject
27+
28+
bb2:
29+
unreachable
30+
}
31+
32+
// CHECK-LABEL: sil [ossa] @test_ossa :
33+
// CHECK: %0 = alloc_ref
34+
// CHECK-O-NEXT: checked_cast_br %0 : $X
35+
// CHECK-ONONE: checked_cast_br %2 : $AnyObject
36+
// CHECK-O: bb2([[A:%.*]] : @owned $X):
37+
// CHECK-O-NEXT: [[U:%.*]] = upcast [[A]] : $X to $B
38+
// CHECK-O-NEXT: [[E:%.*]] = init_existential_ref [[U]] : $B
39+
// CHECK-O-NEXT: destroy_value [[E]]
40+
// CHECK-ONONE: bb2(%{{[0-9]+}} : @owned $AnyObject):
41+
// CHECK: } // end sil function 'test_ossa'
42+
sil [ossa] @test_ossa : $@convention(thin) () -> @owned X {
43+
bb0:
44+
%0 = alloc_ref $X
45+
%1 = upcast %0 : $X to $B
46+
%2 = init_existential_ref %1 : $B : $B, $AnyObject
47+
debug_value %2 : $AnyObject, name "x"
48+
checked_cast_br %2 : $AnyObject to X, bb1, bb2
49+
50+
bb1(%5 : @owned $X):
51+
return %5 : $X
52+
53+
bb2(%7 : @owned $AnyObject):
54+
destroy_value %7: $AnyObject
55+
unreachable
56+
}
57+
58+
// CHECK-LABEL: sil [ossa] @test_ossa_multiple_uses :
59+
// CHECK: checked_cast_br %1 : $B
60+
// CHECK: } // end sil function 'test_ossa_multiple_uses'
61+
sil [ossa] @test_ossa_multiple_uses : $@convention(thin) () -> @owned X {
62+
bb0:
63+
%0 = alloc_ref $X
64+
%1 = upcast %0 : $X to $B
65+
fix_lifetime %1 : $B
66+
checked_cast_br %1 : $B to X, bb1, bb2
67+
68+
bb1(%5 : @owned $X):
69+
return %5 : $X
70+
71+
bb2(%7 : @owned $B):
72+
destroy_value %7: $B
73+
unreachable
74+
}
75+
76+
// CHECK-LABEL: sil [ossa] @test_borrow :
77+
// CHECK: %1 = begin_borrow
78+
// CHECK-NEXT: checked_cast_br %1 : $X
79+
// CHECK: bb2([[A:%.*]] : @guaranteed $X):
80+
// CHECK-NEXT: [[U:%.*]] = upcast [[A]] : $X to $B
81+
// CHECK-NEXT: [[E:%.*]] = init_existential_ref [[U]] : $B
82+
// CHECK-NEXT: fix_lifetime [[E]]
83+
// CHECK: } // end sil function 'test_borrow'
84+
sil [ossa] @test_borrow : $@convention(thin) () -> () {
85+
bb0:
86+
%0 = alloc_ref $X
87+
%1 = begin_borrow %0 : $X
88+
%2 = upcast %1 : $X to $B
89+
%3 = init_existential_ref %2 : $B : $B, $P
90+
checked_cast_br %3 : $P to X, bb1, bb2
91+
92+
bb1(%5 : @guaranteed $X):
93+
end_borrow %1 : $X
94+
destroy_value %0: $X
95+
%8 = tuple ()
96+
return %8 : $()
97+
98+
bb2(%10 : @guaranteed $P):
99+
fix_lifetime %10 : $P
100+
destroy_value %0: $X
101+
unreachable
102+
}
103+
104+
sil_vtable X {
105+
}

0 commit comments

Comments
 (0)