Skip to content

Commit 908f105

Browse files
authored
CSE: disable CSE of lazy property getters of struct (swiftlang#33741)
We cannot prove that the whole struct is overwritten between two lazy property getters. We would need AliasAnalysis for this, but currently this is not used in CSE. rdar://problem/67734844
1 parent 3c81a68 commit 908f105

File tree

3 files changed

+91
-4
lines changed

3 files changed

+91
-4
lines changed

lib/SILOptimizer/Transforms/CSE.cpp

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -834,6 +834,14 @@ static bool isLazyPropertyGetter(ApplyInst *ai) {
834834
!callee->isLazyPropertyGetter())
835835
return false;
836836

837+
// Only handle classes, but not structs.
838+
// Lazy property getters of structs have an indirect inout self parameter.
839+
// We don't know if the whole struct is overwritten between two getter calls.
840+
// In such a case, the lazy property could be reset to an Optional.none.
841+
// TODO: We could check this case with AliasAnalysis.
842+
if (ai->getArgument(0)->getType().isAddress())
843+
return false;
844+
837845
// Check if the first block has a switch_enum of an Optional.
838846
// We don't handle getters of generic types, which have a switch_enum_addr.
839847
// This will be obsolete with opaque values anyway.

test/SILOptimizer/cse.sil

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1312,3 +1312,53 @@ bb0(%0 : $@thick SpecialEnum.Type):
13121312
%4 = struct $Bool (%3 : $Builtin.Int1)
13131313
return %4 : $Bool
13141314
}
1315+
1316+
struct StructWithLazyProperty {
1317+
var lazyProperty: Int64 { mutating get set }
1318+
@_hasStorage @_hasInitialValue var lazyPropertyStorage : Int64? { get set }
1319+
}
1320+
1321+
sil private [lazy_getter] [noinline] @lazy_getter : $@convention(method) (@inout StructWithLazyProperty) -> Int64 {
1322+
bb0(%0 : $*StructWithLazyProperty):
1323+
%2 = struct_element_addr %0 : $*StructWithLazyProperty, #StructWithLazyProperty.lazyPropertyStorage
1324+
%3 = load %2 : $*Optional<Int64>
1325+
switch_enum %3 : $Optional<Int64>, case #Optional.some!enumelt: bb1, case #Optional.none!enumelt: bb2
1326+
1327+
bb1(%5 : $Int64):
1328+
br bb3(%5 : $Int64)
1329+
1330+
bb2:
1331+
%9 = integer_literal $Builtin.Int64, 27
1332+
%10 = struct $Int64 (%9 : $Builtin.Int64)
1333+
%12 = enum $Optional<Int64>, #Optional.some!enumelt, %10 : $Int64
1334+
store %12 to %2 : $*Optional<Int64>
1335+
br bb3(%10 : $Int64)
1336+
1337+
bb3(%15 : $Int64):
1338+
return %15 : $Int64
1339+
}
1340+
1341+
sil @take_int : $@convention(thin) (Int64) -> ()
1342+
1343+
// CHECK-LABEL: sil @dont_cse_lazy_property_of_overwritten_struct : $@convention(thin) () -> () {
1344+
// CHECK: [[GETTER:%[0-9]+]] = function_ref @lazy_getter
1345+
// CHECK: apply [[GETTER]]
1346+
// CHECK: apply [[GETTER]]
1347+
// CHECK: } // end sil function 'dont_cse_lazy_property_of_overwritten_struct'
1348+
sil @dont_cse_lazy_property_of_overwritten_struct : $@convention(thin) () -> () {
1349+
bb0:
1350+
%0 = alloc_stack $StructWithLazyProperty
1351+
%4 = enum $Optional<Int64>, #Optional.none!enumelt
1352+
%5 = struct $StructWithLazyProperty (%4 : $Optional<Int64>)
1353+
store %5 to %0 : $*StructWithLazyProperty
1354+
%7 = function_ref @lazy_getter : $@convention(method) (@inout StructWithLazyProperty) -> Int64
1355+
%8 = apply %7(%0) : $@convention(method) (@inout StructWithLazyProperty) -> Int64
1356+
%9 = function_ref @take_int : $@convention(thin) (Int64) -> ()
1357+
%10 = apply %9(%8) : $@convention(thin) (Int64) -> ()
1358+
store %5 to %0 : $*StructWithLazyProperty
1359+
%18 = apply %7(%0) : $@convention(method) (@inout StructWithLazyProperty) -> Int64
1360+
%20 = apply %9(%18) : $@convention(thin) (Int64) -> ()
1361+
dealloc_stack %0 : $*StructWithLazyProperty
1362+
%22 = tuple ()
1363+
return %22 : $()
1364+
}

test/SILOptimizer/lazy_property_getters.swift

Lines changed: 33 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -87,20 +87,42 @@ func test_no_hoisting(_ c: Myclass, _ b: Bool) -> Int {
8787
return v
8888
}
8989

90+
// This test is disabled, because for structs, it does not work yet.
91+
// CSE is too conservative to handle indirect getter arguments currently.
92+
9093
// CHECK-LABEL: sil {{.*}} @$s4test0A7_structySiAA8MystructVF
94+
// CHECK-DISABLED: [[GETTER:%[0-9]+]] = function_ref @$s4test8MystructV4lvarSivg
95+
// CHECK-DISABLED: [[V1:%[0-9]+]] = apply [[GETTER]]({{.*}})
96+
// CHECK-DISABLED: [[V2OPT:%[0-9]+]] = load
97+
// CHECK-DISABLED: [[V2:%[0-9]+]] = unchecked_enum_data [[V2OPT]]
98+
// CHECK-DISABLED: [[V1VAL:%[0-9]+]] = struct_extract [[V1]]
99+
// CHECK-DISABLED: [[V2VAL:%[0-9]+]] = struct_extract [[V2]]
100+
// CHECK-DISABLED: builtin "sadd{{.*}}"([[V1VAL]] {{.*}}, [[V2VAL]]
101+
// CHECK-DISABLED: } // end sil function '$s4test0A7_structySiAA8MystructVF'
102+
@inline(never)
103+
func test_struct(_ s: Mystruct) -> Int {
104+
var sm = s
105+
g = 42
106+
let v1 = sm.lvar
107+
let v2 = sm.lvar
108+
return v1 &+ v2
109+
}
110+
111+
// CHECK-LABEL: sil {{.*}} @$s4test0A19_overwritten_structySiAA8MystructVF
91112
// CHECK: [[GETTER:%[0-9]+]] = function_ref @$s4test8MystructV4lvarSivg
92113
// CHECK: [[V1:%[0-9]+]] = apply [[GETTER]]({{.*}})
93-
// CHECK: [[V2OPT:%[0-9]+]] = load
94-
// CHECK: [[V2:%[0-9]+]] = unchecked_enum_data [[V2OPT]]
114+
// CHECK: [[V2:%[0-9]+]] = apply [[GETTER]]({{.*}})
95115
// CHECK: [[V1VAL:%[0-9]+]] = struct_extract [[V1]]
96116
// CHECK: [[V2VAL:%[0-9]+]] = struct_extract [[V2]]
97117
// CHECK: builtin "sadd{{.*}}"([[V1VAL]] {{.*}}, [[V2VAL]]
98-
// CHECK: } // end sil function '$s4test0A7_structySiAA8MystructVF'
118+
// CHECK: } // end sil function '$s4test0A19_overwritten_structySiAA8MystructVF'
99119
@inline(never)
100-
func test_struct(_ s: Mystruct) -> Int {
120+
func test_overwritten_struct(_ s: Mystruct) -> Int {
101121
var sm = s
102122
g = 42
103123
let v1 = sm.lvar
124+
sm = s
125+
g = 43
104126
let v2 = sm.lvar
105127
return v1 &+ v2
106128
}
@@ -132,6 +154,13 @@ func calltests() {
132154
// CHECK-OUTPUT-NEXT: lvar init
133155
// CHECK-OUTPUT-NEXT: 84
134156
print(test_struct(Mystruct()))
157+
158+
// CHECK-OUTPUT-LABEL: test_overwritten_struct
159+
print("test_overwritten_struct")
160+
// CHECK-OUTPUT-NEXT: lvar init
161+
// CHECK-OUTPUT-NEXT: lvar init
162+
// CHECK-OUTPUT-NEXT: 85
163+
print(test_overwritten_struct(Mystruct()))
135164
}
136165

137166
calltests()

0 commit comments

Comments
 (0)