Skip to content

Commit f6ad39e

Browse files
committed
use EscapeInfo in ReleaseDevirtualizer
1 parent d78f064 commit f6ad39e

File tree

2 files changed

+131
-150
lines changed

2 files changed

+131
-150
lines changed

SwiftCompilerSources/Sources/Optimizer/FunctionPasses/ReleaseDevirtualizer.swift

Lines changed: 42 additions & 118 deletions
Original file line numberDiff line numberDiff line change
@@ -71,20 +71,32 @@ private func tryDevirtualizeReleaseOfObject(
7171
_ deallocStackRef: DeallocStackRefInst
7272
) {
7373
let allocRefInstruction = deallocStackRef.allocRef
74-
var root = release.operands[0].value
75-
while let newRoot = stripRCIdentityPreservingInsts(root) {
76-
root = newRoot
74+
let type = allocRefInstruction.type
75+
let calleeAnalysis = context.calleeAnalysis
76+
77+
guard let dealloc = calleeAnalysis.getDestructor(ofExactType: type) else {
78+
return
7779
}
7880

79-
if root != allocRefInstruction {
81+
guard let uniqueReference = getUniqueReference(of: release.operand) else {
8082
return
8183
}
8284

83-
let type = allocRefInstruction.type
84-
85-
guard let dealloc = context.calleeAnalysis.getDestructor(ofExactType: type) else {
85+
var escapeInfo = EscapeInfo(calleeAnalysis: calleeAnalysis)
86+
var found = false
87+
if escapeInfo.isEscaping(object: uniqueReference,
88+
visitUse: { op, path, _ in
89+
if op.value === allocRefInstruction {
90+
if !path.isEmpty { return .markEscaping }
91+
found = true
92+
return .ignore
93+
}
94+
if op.value is Allocation { return .markEscaping }
95+
return .continueWalking
96+
}) {
8697
return
8798
}
99+
assert(found, "value must come from an allocation")
88100

89101
let builder = Builder(at: release, location: release.location, context)
90102

@@ -106,119 +118,31 @@ private func tryDevirtualizeReleaseOfObject(
106118
context.erase(instruction: release)
107119
}
108120

109-
private func stripRCIdentityPreservingInsts(_ value: Value) -> Value? {
110-
guard let inst = value as? Instruction else { return nil }
111-
112-
switch inst {
113-
// First strip off RC identity preserving casts.
114-
case is UpcastInst,
115-
is UncheckedRefCastInst,
116-
is InitExistentialRefInst,
117-
is OpenExistentialRefInst,
118-
is RefToBridgeObjectInst,
119-
is BridgeObjectToRefInst,
120-
is ConvertFunctionInst,
121-
is UncheckedEnumDataInst:
122-
return inst.operands[0].value
123-
124-
// Then if we have a struct_extract that is extracting a non-trivial member
125-
// from a struct with no other non-trivial members, a ref count operation on
126-
// the struct is equivalent to a ref count operation on the extracted
127-
// member. Strip off the extract.
128-
case let sei as StructExtractInst where sei.isFieldOnlyNonTrivialField:
129-
return sei.operand
130-
131-
// If we have a struct or tuple instruction with only one non-trivial operand, the
132-
// only reference count that can be modified is the non-trivial operand. Return
133-
// the non-trivial operand.
134-
case is StructInst, is TupleInst:
135-
return inst.uniqueNonTrivialOperand
136-
137-
// If we have an enum instruction with a payload, strip off the enum to
138-
// expose the enum's payload.
139-
case let ei as EnumInst where !ei.operands.isEmpty:
140-
return ei.operand
141-
142-
// If we have a tuple_extract that is extracting the only non trivial member
143-
// of a tuple, a retain_value on the tuple is equivalent to a retain_value on
144-
// the extracted value.
145-
case let tei as TupleExtractInst where tei.isEltOnlyNonTrivialElt:
146-
return tei.operand
147-
148-
default:
149-
return nil
150-
}
151-
}
152-
153-
private extension Instruction {
154-
/// Search the operands of this tuple for a unique non-trivial elt. If we find
155-
/// it, return it. Otherwise return `nil`.
156-
var uniqueNonTrivialOperand: Value? {
157-
var candidateElt: Value?
158-
let function = self.function
159-
160-
for op in operands {
161-
if !op.value.type.isTrivial(in: function) {
162-
if candidateElt == nil {
163-
candidateElt = op.value
164-
continue
121+
private func getUniqueReference(of value: Value) -> Value? {
122+
let function = value.function
123+
var val = value
124+
while true {
125+
if val.type.isClass {
126+
return val
127+
}
128+
switch val {
129+
case let ei as EnumInst where !ei.operands.isEmpty:
130+
val = ei.operand
131+
case is StructInst, is TupleInst:
132+
var uniqueNonTrivialOperand: Value?
133+
134+
for op in (val as! SingleValueInstruction).operands {
135+
if !op.value.type.isTrivial(in: function) {
136+
if let _ = uniqueNonTrivialOperand {
137+
return nil
138+
}
139+
uniqueNonTrivialOperand = op.value
140+
}
165141
}
166-
167-
// Otherwise, we have two values that are non-trivial. Bail.
142+
guard let uniqueOp = uniqueNonTrivialOperand else { return nil }
143+
val = uniqueOp
144+
default:
168145
return nil
169-
}
170146
}
171-
172-
return candidateElt
173-
}
174-
}
175-
176-
private extension TupleExtractInst {
177-
var isEltOnlyNonTrivialElt: Bool {
178-
let function = self.function
179-
180-
if type.isTrivial(in: function) {
181-
return false
182-
}
183-
184-
let opType = operand.type
185-
186-
var nonTrivialEltsCount = 0
187-
for elt in opType.tupleElements {
188-
if elt.isTrivial(in: function) {
189-
nonTrivialEltsCount += 1
190-
}
191-
192-
if nonTrivialEltsCount > 1 {
193-
return false
194-
}
195-
}
196-
197-
return true
198-
}
199-
}
200-
201-
private extension StructExtractInst {
202-
var isFieldOnlyNonTrivialField: Bool {
203-
let function = self.function
204-
205-
if type.isTrivial(in: function) {
206-
return false
207-
}
208-
209-
let structType = operand.type
210-
211-
var nonTrivialFieldsCount = 0
212-
for field in structType.getNominalFields(in: function) {
213-
if field.isTrivial(in: function) {
214-
nonTrivialFieldsCount += 1
215-
}
216-
217-
if nonTrivialFieldsCount > 1 {
218-
return false
219-
}
220-
}
221-
222-
return true
223147
}
224148
}

test/SILOptimizer/devirt_release.sil

Lines changed: 89 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,12 @@ import Builtin
1111
import Swift
1212

1313
class B {
14+
@_hasStorage var next: B
1415
}
1516

17+
struct Str {
18+
@_hasStorage var b: B
19+
}
1620

1721
// CHECK-LABEL: sil @devirtualize_object
1822
// CHECK: [[A:%[0-9]+]] = alloc_ref
@@ -21,7 +25,7 @@ class B {
2125
// CHECK: [[D:%[0-9]+]] = function_ref @$s4test1BCfD
2226
// CHECK-NEXT: apply [[D]]([[A]])
2327
// CHECK-NEXT: dealloc_stack_ref [[A]]
24-
// CHECK: return
28+
// CHECK: } // end sil function 'devirtualize_object'
2529
sil @devirtualize_object : $@convention(thin) () -> () {
2630
bb0:
2731
%1 = alloc_ref [stack] $B
@@ -36,6 +40,7 @@ bb0:
3640
// CHECK-NEXT: strong_release
3741
// CHECK-NEXT: dealloc_ref
3842
// CHECK: return
43+
// CHECK: } // end sil function 'dont_devirtualize_heap_allocated'
3944
sil @dont_devirtualize_heap_allocated : $@convention(thin) () -> () {
4045
bb0:
4146
%1 = alloc_ref $B
@@ -72,6 +77,7 @@ bb0:
7277
// CHECK-NEXT: strong_release
7378
// CHECK-NEXT: dealloc_ref
7479
// CHECK: return
80+
// CHECK: } // end sil function 'dont_devirtualize_wrong_release'
7581
sil @dont_devirtualize_wrong_release : $@convention(thin) (@owned B) -> () {
7682
bb0(%0 : $B):
7783
%1 = alloc_ref $B
@@ -88,7 +94,7 @@ bb0(%0 : $B):
8894
// CHECK: [[F:%[0-9]+]] = function_ref @unknown_func
8995
// CHECK-NEXT: apply [[F]]()
9096
// CHECK-NEXT: dealloc_ref
91-
// CHECK: return
97+
// CHECK: } // end sil function 'dont_devirtualize_unknown_release'
9298
sil @dont_devirtualize_unknown_release : $@convention(thin) (@owned B) -> () {
9399
bb0(%0 : $B):
94100
%1 = alloc_ref $B
@@ -107,7 +113,7 @@ bb0(%0 : $B):
107113
// CHECK: [[D:%[0-9]+]] = function_ref @$s4test1BCfD
108114
// CHECK-NEXT: apply [[D]]([[A]])
109115
// CHECK-NEXT: dealloc_stack_ref [[A]]
110-
// CHECK: return
116+
// CHECK: } // end sil function 'dont_crash_with_missing_release'
111117
sil @dont_crash_with_missing_release : $@convention(thin) () -> () {
112118
bb0:
113119
%1 = alloc_ref [stack] $B
@@ -119,42 +125,93 @@ bb0:
119125
return %r : $()
120126
}
121127

128+
// CHECK-LABEL: sil @long_chain_from_alloc_to_release
129+
// CHECK: set_deallocating
130+
// CHECK-NOT: release_value
131+
// CHECK: } // end sil function 'long_chain_from_alloc_to_release'
132+
sil @long_chain_from_alloc_to_release : $@convention(thin) () -> () {
133+
bb0:
134+
%1 = alloc_ref [stack] $B
135+
%2 = struct $Str(%1 : $B)
136+
%3 = struct_extract %2 : $Str, #Str.b
137+
cond_br undef, bb1, bb2
138+
139+
bb1:
140+
br bb3(%3 : $B)
141+
142+
bb2:
143+
br bb3(%3 : $B)
144+
145+
bb3(%a : $B):
146+
%s = struct $Str (%a : $B)
147+
%i = integer_literal $Builtin.Int64, 0
148+
%t = tuple (%s : $Str, %i : $Builtin.Int64)
149+
%o = enum $Optional<(Str, Builtin.Int64)>, #Optional.some!enumelt, %t : $(Str, Builtin.Int64)
150+
release_value %o : $Optional<(Str, Builtin.Int64)>
151+
dealloc_stack_ref %1 : $B
152+
%r = tuple ()
153+
return %r : $()
154+
}
122155

123-
sil @unknown_func : $@convention(thin) () -> ()
124-
125-
// test.B.__deallocating_deinit
126-
sil hidden @$s4test1BCfD : $@convention(method) (@owned B) -> () {
127-
// %0 // users: %1, %3
128-
bb0(%0 : $B):
129-
debug_value %0 : $B, let, name "self" // id: %1
130-
// function_ref test.B.deinit
131-
%2 = function_ref @$s4test1BCfd : $@convention(method) (@guaranteed B) -> @owned Builtin.NativeObject // user: %3
132-
%3 = apply %2(%0) : $@convention(method) (@guaranteed B) -> @owned Builtin.NativeObject // user: %4
133-
%4 = unchecked_ref_cast %3 : $Builtin.NativeObject to $B // user: %5
134-
dealloc_ref %4 : $B // id: %5
135-
%6 = tuple () // user: %7
136-
return %6 : $() // id: %7
156+
// CHECK-LABEL: sil @dont_devirtualize_double_release
157+
// CHECK-NOT: set_deallocating
158+
// CHECK: release_value
159+
// CHECK: } // end sil function 'dont_devirtualize_double_release'
160+
sil @dont_devirtualize_double_release : $@convention(thin) () -> () {
161+
bb0:
162+
%1 = alloc_ref [stack] $B
163+
%t = tuple (%1 : $B, %1 : $B)
164+
release_value %t : $(B, B)
165+
dealloc_stack_ref %1 : $B
166+
%r = tuple ()
167+
return %r : $()
137168
}
138169

139-
// test.B.deinit
140-
sil hidden @$s4test1BCfd : $@convention(method) (@guaranteed B) -> @owned Builtin.NativeObject {
141-
// %0 // users: %1, %2
142-
bb0(%0 : $B):
143-
debug_value %0 : $B, let, name "self" // id: %1
144-
%2 = unchecked_ref_cast %0 : $B to $Builtin.NativeObject // user: %3
145-
return %2 : $Builtin.NativeObject // id: %3
170+
// CHECK-LABEL: sil @dont_devirtualize_release_of_load
171+
// CHECK-NOT: set_deallocating
172+
// CHECK: strong_release
173+
// CHECK: } // end sil function 'dont_devirtualize_release_of_load'
174+
sil @dont_devirtualize_release_of_load : $@convention(thin) () -> () {
175+
bb0:
176+
%1 = alloc_ref [stack] $B
177+
%2 = ref_element_addr %1 : $B, #B.next
178+
%3 = load %2 : $*B
179+
strong_release %3 : $B
180+
dealloc_stack_ref %1 : $B
181+
%r = tuple ()
182+
return %r : $()
146183
}
147184

148-
// test.B.init () -> test.B
149-
sil hidden @$s4test1BCACycfc : $@convention(method) (@owned B) -> @owned B {
150-
// %0 // users: %1, %2
151-
bb0(%0 : $B):
152-
debug_value %0 : $B, let, name "self" // id: %1
153-
return %0 : $B // id: %2
185+
// CHECK-LABEL: sil @dont_devirtualize_release_of_different_allocations
186+
// CHECK-NOT: set_deallocating
187+
// CHECK: strong_release
188+
// CHECK: } // end sil function 'dont_devirtualize_release_of_different_allocations'
189+
sil @dont_devirtualize_release_of_different_allocations : $@convention(thin) () -> () {
190+
bb0:
191+
%1 = alloc_ref [stack] $B
192+
%2 = alloc_ref [stack] $B
193+
cond_br undef, bb1, bb2
194+
195+
bb1:
196+
br bb3(%1 : $B)
197+
198+
bb2:
199+
br bb3(%2 : $B)
200+
201+
bb3(%a : $B):
202+
strong_release %a : $B
203+
dealloc_stack_ref %2 : $B
204+
dealloc_stack_ref %1 : $B
205+
%r = tuple ()
206+
return %r : $()
154207
}
155208

209+
210+
sil @unknown_func : $@convention(thin) () -> ()
211+
212+
sil @$s4test1BCfD : $@convention(method) (@owned B) -> ()
213+
156214
sil_vtable B {
157-
#B.deinit!deallocator: @$s4test1BCfD // test.B.__deallocating_deinit
158-
#B.init!initializer: @$s4test1BCACycfc // test.B.init () -> test.B
215+
#B.deinit!deallocator: @$s4test1BCfD
159216
}
160217

0 commit comments

Comments
 (0)