Skip to content

Commit c452e4c

Browse files
committed
DefiniteInitialization: correctly handle implicit closures.
Correctly handle implicit closures in initializers, e.g. with boolean operators: init() { bool_member1 = false bool_member2 = false || bool_member1 // implicit closure } The implicit closure ('bool_member1' at the RHS of the || operator) captures the whole self, but only uses 'bool_member1'. If the whole captured 'self' is considered as use, we would get a "'self.bool_member2' not initialized" error at the partial_apply. Therefore look into the body of the closure and only add the actually used members. rdar://66420045
1 parent 27f670b commit c452e4c

File tree

4 files changed

+335
-11
lines changed

4 files changed

+335
-11
lines changed

lib/SILOptimizer/Mandatory/DIMemoryUseCollector.cpp

Lines changed: 116 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -475,9 +475,14 @@ namespace {
475475
/// Gathers information about a specific address and its uses to determine
476476
/// definite initialization.
477477
class ElementUseCollector {
478+
public:
479+
typedef SmallPtrSet<SILFunction *, 8> FunctionSet;
480+
481+
private:
478482
SILModule &Module;
479483
const DIMemoryObjectInfo &TheMemory;
480484
DIElementUseInfo &UseInfo;
485+
FunctionSet &VisitedClosures;
481486

482487
/// IsSelfOfNonDelegatingInitializer - This is true if we're looking at the
483488
/// top level of a 'self' variable in a non-delegating init method.
@@ -494,12 +499,15 @@ class ElementUseCollector {
494499

495500
public:
496501
ElementUseCollector(const DIMemoryObjectInfo &TheMemory,
497-
DIElementUseInfo &UseInfo)
498-
: Module(TheMemory.getModule()), TheMemory(TheMemory), UseInfo(UseInfo) {}
502+
DIElementUseInfo &UseInfo,
503+
FunctionSet &visitedClosures)
504+
: Module(TheMemory.getModule()), TheMemory(TheMemory), UseInfo(UseInfo),
505+
VisitedClosures(visitedClosures)
506+
{}
499507

500508
/// This is the main entry point for the use walker. It collects uses from
501509
/// the address and the refcount result of the allocation.
502-
void collectFrom() {
510+
void collectFrom(SILValue V, bool collectDestroysOfContainer) {
503511
IsSelfOfNonDelegatingInitializer = TheMemory.isNonDelegatingInit();
504512

505513
// If this is a delegating initializer, collect uses specially.
@@ -508,12 +516,16 @@ class ElementUseCollector {
508516
assert(!TheMemory.isDerivedClassSelfOnly() &&
509517
"Should have been handled outside of here");
510518
// If this is a class pointer, we need to look through ref_element_addrs.
511-
collectClassSelfUses();
519+
collectClassSelfUses(V);
512520
return;
513521
}
514522

515-
collectUses(TheMemory.getUninitializedValue(), 0);
516-
gatherDestroysOfContainer(TheMemory, UseInfo);
523+
collectUses(V, 0);
524+
if (collectDestroysOfContainer) {
525+
assert(V == TheMemory.getUninitializedValue() &&
526+
"can only gather destroys of root value");
527+
gatherDestroysOfContainer(TheMemory, UseInfo);
528+
}
517529
}
518530

519531
void trackUse(DIMemoryUse Use) { UseInfo.trackUse(Use); }
@@ -525,7 +537,9 @@ class ElementUseCollector {
525537

526538
private:
527539
void collectUses(SILValue Pointer, unsigned BaseEltNo);
528-
void collectClassSelfUses();
540+
bool addClosureElementUses(PartialApplyInst *pai, Operand *argUse);
541+
542+
void collectClassSelfUses(SILValue ClassPointer);
529543
void collectClassSelfUses(SILValue ClassPointer, SILType MemorySILType,
530544
llvm::SmallDenseMap<VarDecl *, unsigned> &EN);
531545

@@ -911,9 +925,15 @@ void ElementUseCollector::collectUses(SILValue Pointer, unsigned BaseEltNo) {
911925
continue;
912926
}
913927

928+
if (User->isDebugInstruction())
929+
continue;
930+
914931
if (auto *PAI = dyn_cast<PartialApplyInst>(User)) {
915932
if (onlyUsedByAssignByWrapper(PAI))
916933
continue;
934+
935+
if (BaseEltNo == 0 && addClosureElementUses(PAI, Op))
936+
continue;
917937
}
918938

919939
// Sanitizer instrumentation is not user visible, so it should not
@@ -926,9 +946,85 @@ void ElementUseCollector::collectUses(SILValue Pointer, unsigned BaseEltNo) {
926946
}
927947
}
928948

949+
/// Add all used elements of an implicit closure, which is capturing 'self'.
950+
///
951+
/// We want to correctly handle implicit closures in initializers, e.g. with
952+
/// boolean operators:
953+
/// \code
954+
/// init() {
955+
/// bool_member1 = false
956+
/// bool_member2 = false || bool_member1 // implicit closure
957+
/// }
958+
/// \endcode
959+
///
960+
/// The implicit closure ('bool_member1' at the RHS of the || operator) captures
961+
/// the whole self, but only uses 'bool_member1'.
962+
/// If we would add the whole captured 'self' as use, we would get a
963+
/// "'self.bool_member2' not initialized" error at the partial_apply.
964+
/// Therefore we look into the body of the closure and only add the actually
965+
/// used members.
966+
bool ElementUseCollector::addClosureElementUses(PartialApplyInst *pai,
967+
Operand *argUse) {
968+
SILFunction *callee = pai->getReferencedFunctionOrNull();
969+
if (!callee)
970+
return false;
971+
972+
// Implicit closures are "transparent", which means they are always inlined.
973+
// It would probably also work to handle non-transparent closures (e.g.
974+
// explicit closures). But if the closure is not inlined we could end up
975+
// passing a partially initialized self to the closure function. Although it
976+
// would probably not cause any real problems, an `@in_guaranteed` argument
977+
// (the captured 'self') is assumed to be fully initialized in SIL.
978+
if (!callee->isTransparent())
979+
return false;
980+
981+
// Implicit closures are only partial-applied once and there cannot be a
982+
// recursive cycle of implicit closures.
983+
// Nevertheless such a scenario is theoretically possible in SIL. To be on the
984+
// safe side, check for cycles.
985+
if (!VisitedClosures.insert(callee).second)
986+
return false;
987+
988+
unsigned argIndex = ApplySite(pai).getCalleeArgIndex(*argUse);
989+
SILArgument *arg = callee->getArgument(argIndex);
990+
991+
// Bail if arg is not the original 'self' object, but e.g. a projected member.
992+
assert(TheMemory.getType().isObject());
993+
if (arg->getType().getObjectType() != TheMemory.getType())
994+
return false;
995+
996+
DIElementUseInfo ArgUseInfo;
997+
ElementUseCollector collector(TheMemory, ArgUseInfo, VisitedClosures);
998+
collector.collectFrom(arg, /*collectDestroysOfContainer*/ false);
999+
1000+
if (!ArgUseInfo.Releases.empty() || !ArgUseInfo.StoresToSelf.empty())
1001+
return false;
1002+
1003+
for (const DIMemoryUse &use : ArgUseInfo.Uses) {
1004+
// Only handle loads and escapes. Implicit closures will not have stores or
1005+
// store-like uses, anyway.
1006+
// Also, as we don't do a flow-sensitive analysis of the callee, we cannot
1007+
// handle stores, because we don't know if they are unconditional or not.
1008+
switch (use.Kind) {
1009+
case DIUseKind::Load:
1010+
case DIUseKind::Escape:
1011+
case DIUseKind::InOutArgument:
1012+
break;
1013+
default:
1014+
return false;
1015+
}
1016+
}
1017+
1018+
// Track all uses of the closure.
1019+
for (const DIMemoryUse &use : ArgUseInfo.Uses) {
1020+
trackUse(DIMemoryUse(pai, use.Kind, use.FirstElement, use.NumElements));
1021+
}
1022+
return true;
1023+
}
1024+
9291025
/// collectClassSelfUses - Collect all the uses of a 'self' pointer in a class
9301026
/// constructor. The memory object has class type.
931-
void ElementUseCollector::collectClassSelfUses() {
1027+
void ElementUseCollector::collectClassSelfUses(SILValue ClassPointer) {
9321028
assert(IsSelfOfNonDelegatingInitializer &&
9331029
TheMemory.getASTType()->getClassOrBoundGenericClass() != nullptr);
9341030

@@ -952,8 +1048,7 @@ void ElementUseCollector::collectClassSelfUses() {
9521048
// If we are looking at the init method for a root class, just walk the
9531049
// MUI use-def chain directly to find our uses.
9541050
if (TheMemory.isRootSelf()) {
955-
collectClassSelfUses(TheMemory.getUninitializedValue(), TheMemory.getType(),
956-
EltNumbering);
1051+
collectClassSelfUses(ClassPointer, TheMemory.getType(), EltNumbering);
9571052
return;
9581053
}
9591054

@@ -1307,11 +1402,18 @@ void ElementUseCollector::collectClassSelfUses(
13071402
if (isUninitializedMetatypeInst(User))
13081403
continue;
13091404

1405+
if (User->isDebugInstruction())
1406+
continue;
1407+
13101408
// If this is a partial application of self, then this is an escape point
13111409
// for it.
13121410
if (auto *PAI = dyn_cast<PartialApplyInst>(User)) {
13131411
if (onlyUsedByAssignByWrapper(PAI))
13141412
continue;
1413+
1414+
if (addClosureElementUses(PAI, Op))
1415+
continue;
1416+
13151417
Kind = DIUseKind::Escape;
13161418
}
13171419

@@ -1709,5 +1811,8 @@ void swift::ownership::collectDIElementUsesFrom(
17091811
return;
17101812
}
17111813

1712-
ElementUseCollector(MemoryInfo, UseInfo).collectFrom();
1814+
ElementUseCollector::FunctionSet VisitedClosures;
1815+
ElementUseCollector collector(MemoryInfo, UseInfo, VisitedClosures);
1816+
collector.collectFrom(MemoryInfo.getUninitializedValue(),
1817+
/*collectDestroysOfContainer*/ true);
17131818
}
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
// RUN: %target-sil-opt -enable-sil-verify-all %s -definite-init
2+
3+
// Test that implicit closures don't count as use of the whole self and only
4+
// actual uses inside the closures are taken into account.
5+
6+
// Check that no errors are generated. No FileCheck needed.
7+
8+
sil_stage raw
9+
10+
import Builtin
11+
import Swift
12+
import SwiftShims
13+
14+
struct SimpleStruct {
15+
@_hasStorage let x: Bool
16+
@_hasStorage let y: Bool
17+
init()
18+
}
19+
20+
class SimpleClass {
21+
@_hasStorage final let x: Bool
22+
@_hasStorage final let y: Bool
23+
init()
24+
}
25+
26+
sil [ossa] @$s4test1SVACycfC : $@convention(method) (Bool) -> SimpleStruct {
27+
bb0(%0 : $Bool):
28+
%1 = alloc_stack $SimpleStruct, var, name "self"
29+
%2 = mark_uninitialized [rootself] %1 : $*SimpleStruct
30+
%7 = begin_access [modify] [static] %2 : $*SimpleStruct
31+
%8 = struct_element_addr %7 : $*SimpleStruct, #SimpleStruct.x
32+
assign %0 to %8 : $*Bool
33+
end_access %7 : $*SimpleStruct
34+
%16 = function_ref @implicit_closure_struct : $@convention(thin) (@inout_aliasable SimpleStruct) -> (Bool, @error Error)
35+
%17 = partial_apply [callee_guaranteed] %16(%2) : $@convention(thin) (@inout_aliasable SimpleStruct) -> (Bool, @error Error)
36+
destroy_value %17 : $@callee_guaranteed () -> (Bool, @error Error)
37+
%23 = begin_access [modify] [static] %2 : $*SimpleStruct
38+
%24 = struct_element_addr %23 : $*SimpleStruct, #SimpleStruct.y
39+
assign %0 to %24 : $*Bool
40+
end_access %23 : $*SimpleStruct
41+
%27 = load [trivial] %2 : $*SimpleStruct
42+
dealloc_stack %1 : $*SimpleStruct
43+
return %27 : $SimpleStruct
44+
}
45+
46+
sil private [transparent] [ossa] @implicit_closure_struct : $@convention(thin) (@inout_aliasable SimpleStruct) -> (Bool, @error Error) {
47+
bb0(%0 : $*SimpleStruct):
48+
debug_value_addr %0 : $*SimpleStruct, var, name "self", argno 2
49+
%3 = begin_access [read] [static] %0 : $*SimpleStruct
50+
%4 = struct_element_addr %3 : $*SimpleStruct, #SimpleStruct.x
51+
%5 = load [trivial] %4 : $*Bool
52+
end_access %3 : $*SimpleStruct
53+
return %5 : $Bool
54+
}
55+
56+
sil [ossa] @$s4test11SimpleClassC1bACSb_tcfc : $@convention(method) (Bool, @owned SimpleClass) -> @owned SimpleClass {
57+
bb0(%0 : $Bool, %1 : @owned $SimpleClass):
58+
%4 = mark_uninitialized [rootself] %1 : $SimpleClass
59+
%5 = begin_borrow %4 : $SimpleClass
60+
%6 = ref_element_addr %5 : $SimpleClass, #SimpleClass.x
61+
assign %0 to %6 : $*Bool
62+
end_borrow %5 : $SimpleClass
63+
%9 = begin_borrow %4 : $SimpleClass
64+
%11 = function_ref @implicit_closure_class : $@convention(thin) (@guaranteed SimpleClass) -> (Bool, @error Error)
65+
%12 = copy_value %4 : $SimpleClass
66+
%13 = partial_apply [callee_guaranteed] %11(%12) : $@convention(thin) (@guaranteed SimpleClass) -> (Bool, @error Error)
67+
destroy_value %13 : $@callee_guaranteed () -> (Bool, @error Error)
68+
%19 = ref_element_addr %9 : $SimpleClass, #SimpleClass.y
69+
assign %0 to %19 : $*Bool
70+
end_borrow %9 : $SimpleClass
71+
return %4 : $SimpleClass
72+
}
73+
74+
sil private [transparent] [ossa] @implicit_closure_class : $@convention(thin) (@guaranteed SimpleClass) -> (Bool, @error Error) {
75+
bb0(%0 : @guaranteed $SimpleClass):
76+
debug_value %0 : $SimpleClass, let, name "self", argno 2
77+
%3 = ref_element_addr %0 : $SimpleClass, #SimpleClass.x
78+
%4 = load [trivial] %3 : $*Bool
79+
return %4 : $Bool
80+
}
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
// RUN: %target-swift-frontend -emit-sil %s -o /dev/null
2+
3+
// Test boolean operators with implicit closures
4+
5+
struct Simple {
6+
let x: Bool
7+
let y: Bool
8+
9+
init() {
10+
x = false
11+
y = false || x
12+
}
13+
}
14+
15+
struct NestedClosures {
16+
let x: Bool
17+
let y: Bool
18+
let z: Bool
19+
20+
init(a: Bool) {
21+
x = false
22+
y = false
23+
z = false || (y || (x || a))
24+
}
25+
26+
init(b: Bool) {
27+
x = false
28+
y = false
29+
// With explicit self
30+
z = false || (self.y || (self.x || b))
31+
}
32+
}
33+
34+
class SimpleClass {
35+
let x: Bool
36+
let y: Bool
37+
38+
init() {
39+
x = false
40+
y = false || x
41+
}
42+
}
43+
44+
func forward(_ b: inout Bool) -> Bool {
45+
return b
46+
}
47+
48+
struct InoutUse {
49+
var x: Bool
50+
var y: Bool
51+
52+
init() {
53+
x = false
54+
y = false || forward(&x)
55+
}
56+
}
57+
58+
protocol P {
59+
var b: Bool { get }
60+
}
61+
62+
struct Generic<T : P> {
63+
let x: T
64+
let y: Bool
65+
66+
init(_ t: T) {
67+
x = t
68+
y = false || x.b
69+
}
70+
}
71+
72+

0 commit comments

Comments
 (0)