Skip to content

Commit 746b58e

Browse files
authored
Merge pull request #28917 from atrick/fix-escape
Fix a crash in StackPromotion; case not handled in EscapeAnalysis.
2 parents 2007515 + 786c29a commit 746b58e

File tree

3 files changed

+144
-66
lines changed

3 files changed

+144
-66
lines changed

include/swift/SILOptimizer/Analysis/EscapeAnalysis.h

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1009,12 +1009,12 @@ class EscapeAnalysis : public BottomUpIPAnalysis {
10091009
/// If EscapeAnalysis should consider the given value to be a derived address
10101010
/// or pointer based on one of its address or pointer operands, then return
10111011
/// that operand value. Otherwise, return an invalid value.
1012-
SILValue getPointerBase(SILValue value) const;
1012+
SILValue getPointerBase(SILValue value);
10131013

10141014
/// Recursively find the given value's pointer base. If the value cannot be
10151015
/// represented in EscapeAnalysis as one of its operands, then return the same
10161016
/// value.
1017-
SILValue getPointerRoot(SILValue value) const;
1017+
SILValue getPointerRoot(SILValue value);
10181018

10191019
PointerKind findRecursivePointerKind(SILType Ty, const SILFunction &F) const;
10201020

@@ -1055,7 +1055,31 @@ class EscapeAnalysis : public BottomUpIPAnalysis {
10551055
void buildConnectionGraph(FunctionInfo *FInfo, FunctionOrder &BottomUpOrder,
10561056
int RecursionDepth);
10571057

1058-
bool createArrayUninitializedSubgraph(FullApplySite apply,
1058+
// @_semantics("array.uninitialized") takes a reference to the storage and
1059+
// returns an instantiated array struct and unsafe pointer to the elements.
1060+
struct ArrayUninitCall {
1061+
SILValue arrayStorageRef;
1062+
TupleExtractInst *arrayStruct = nullptr;
1063+
TupleExtractInst *arrayElementPtr = nullptr;
1064+
1065+
bool isValid() const {
1066+
return arrayStorageRef && arrayStruct && arrayElementPtr;
1067+
}
1068+
};
1069+
1070+
/// If \p ai is an optimizable @_semantics("array.uninitialized") call, return
1071+
/// valid call information.
1072+
ArrayUninitCall canOptimizeArrayUninitializedCall(ApplyInst *ai,
1073+
ConnectionGraph *conGraph);
1074+
1075+
/// Return true of this tuple_extract is the result of an optimizable
1076+
/// @_semantics("array.uninitialized") call.
1077+
bool canOptimizeArrayUninitializedResult(TupleExtractInst *tei);
1078+
1079+
/// Handle a call to "@_semantics(array.uninitialized") precisely by mapping
1080+
/// each call result to a separate graph node and relating them to the
1081+
/// argument.
1082+
void createArrayUninitializedSubgraph(ArrayUninitCall call,
10591083
ConnectionGraph *conGraph);
10601084

10611085
/// Updates the graph by analyzing instruction \p I.

lib/SILOptimizer/Analysis/EscapeAnalysis.cpp

Lines changed: 73 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -110,18 +110,10 @@ EscapeAnalysis::findCachedPointerKind(SILType Ty, const SILFunction &F) const {
110110
return pointerKind;
111111
}
112112

113-
static bool isExtractOfArrayUninitializedPointer(TupleExtractInst *TEI) {
114-
if (auto apply = dyn_cast<ApplyInst>(TEI->getOperand()))
115-
if (ArraySemanticsCall(apply, "array.uninitialized", false))
116-
return true;
117-
118-
return false;
119-
}
120-
121113
// If EscapeAnalysis should consider the given value to be a derived address or
122114
// pointer based on one of its address or pointer operands, then return that
123115
// operand value. Otherwise, return an invalid value.
124-
SILValue EscapeAnalysis::getPointerBase(SILValue value) const {
116+
SILValue EscapeAnalysis::getPointerBase(SILValue value) {
125117
switch (value->getKind()) {
126118
case ValueKind::IndexAddrInst:
127119
case ValueKind::IndexRawPointerInst:
@@ -156,10 +148,8 @@ SILValue EscapeAnalysis::getPointerBase(SILValue value) const {
156148
case ValueKind::TupleExtractInst: {
157149
auto *TEI = cast<TupleExtractInst>(value);
158150
// Special handling for extracting the pointer-result from an
159-
// array construction. We handle this like a ref_element_addr
160-
// rather than a projection. See the handling of tuple_extract
161-
// in analyzeInstruction().
162-
if (isExtractOfArrayUninitializedPointer(TEI))
151+
// array construction. See createArrayUninitializedSubgraph.
152+
if (canOptimizeArrayUninitializedResult(TEI))
163153
return SILValue();
164154
return TEI->getOperand();
165155
}
@@ -188,7 +178,7 @@ SILValue EscapeAnalysis::getPointerBase(SILValue value) const {
188178
// Recursively find the given value's pointer base. If the value cannot be
189179
// represented in EscapeAnalysis as one of its operands, then return the same
190180
// value.
191-
SILValue EscapeAnalysis::getPointerRoot(SILValue value) const {
181+
SILValue EscapeAnalysis::getPointerRoot(SILValue value) {
192182
while (true) {
193183
if (SILValue v2 = getPointerBase(value))
194184
value = v2;
@@ -1721,28 +1711,6 @@ void EscapeAnalysis::buildConnectionGraph(FunctionInfo *FInfo,
17211711
<< FInfo->Graph.F->getName() << '\n');
17221712
}
17231713

1724-
/// Returns the tuple extract for the first two fields if all uses of \p I are
1725-
/// tuple_extract instructions.
1726-
static std::pair<TupleExtractInst *, TupleExtractInst *>
1727-
onlyUsedInTupleExtract(SILValue V) {
1728-
TupleExtractInst *field0 = nullptr;
1729-
TupleExtractInst *field1 = nullptr;
1730-
for (Operand *Use : getNonDebugUses(V)) {
1731-
if (auto *TEI = dyn_cast<TupleExtractInst>(Use->getUser())) {
1732-
if (TEI->getFieldNo() == 0) {
1733-
field0 = TEI;
1734-
continue;
1735-
}
1736-
if (TEI->getFieldNo() == 1) {
1737-
field1 = TEI;
1738-
continue;
1739-
}
1740-
}
1741-
return std::make_pair(nullptr, nullptr);
1742-
}
1743-
return std::make_pair(field0, field1);
1744-
}
1745-
17461714
bool EscapeAnalysis::buildConnectionGraphForCallees(
17471715
SILInstruction *Caller, CalleeList Callees, FunctionInfo *FInfo,
17481716
FunctionOrder &BottomUpOrder, int RecursionDepth) {
@@ -1799,38 +1767,79 @@ bool EscapeAnalysis::buildConnectionGraphForDestructor(
17991767
RecursionDepth);
18001768
}
18011769

1802-
// Handle array.uninitialized
1803-
bool EscapeAnalysis::createArrayUninitializedSubgraph(
1804-
FullApplySite apply, ConnectionGraph *conGraph) {
1770+
EscapeAnalysis::ArrayUninitCall
1771+
EscapeAnalysis::canOptimizeArrayUninitializedCall(ApplyInst *ai,
1772+
ConnectionGraph *conGraph) {
1773+
ArrayUninitCall call;
1774+
// This must be an exact match so we don't accidentally optimize
1775+
// "array.uninitialized_intrinsic".
1776+
if (!ArraySemanticsCall(ai, "array.uninitialized", false))
1777+
return call;
18051778

18061779
// Check if the result is used in the usual way: extracting the
18071780
// array and the element pointer with tuple_extract.
1808-
TupleExtractInst *arrayStruct;
1809-
TupleExtractInst *arrayElementPtr;
1810-
std::tie(arrayStruct, arrayElementPtr) =
1811-
onlyUsedInTupleExtract(cast<ApplyInst>(apply.getInstruction()));
1812-
if (!arrayStruct || !arrayElementPtr)
1781+
for (Operand *use : getNonDebugUses(ai)) {
1782+
if (auto *tei = dyn_cast<TupleExtractInst>(use->getUser())) {
1783+
if (tei->getFieldNo() == 0) {
1784+
call.arrayStruct = tei;
1785+
continue;
1786+
}
1787+
if (tei->getFieldNo() == 1) {
1788+
call.arrayElementPtr = tei;
1789+
continue;
1790+
}
1791+
}
1792+
// If there are any other uses, such as a release_value, erase the previous
1793+
// call info and bail out.
1794+
call.arrayStruct = nullptr;
1795+
call.arrayElementPtr = nullptr;
1796+
break;
1797+
}
1798+
// An "array.uninitialized" call may have a first argument which is the
1799+
// allocated array buffer. Make sure the call's argument is recognized by
1800+
// EscapeAnalysis as a pointer, otherwise createArrayUninitializedSubgraph
1801+
// won't be able to map the result nodes onto it. There is a variant of
1802+
// @_semantics("array.uninitialized") that does not take the storage as input,
1803+
// so it will effectively bail out here.
1804+
if (isPointer(ai->getArgument(0)))
1805+
call.arrayStorageRef = ai->getArgument(0);
1806+
return call;
1807+
}
1808+
1809+
bool EscapeAnalysis::canOptimizeArrayUninitializedResult(
1810+
TupleExtractInst *tei) {
1811+
ApplyInst *ai = dyn_cast<ApplyInst>(tei->getOperand());
1812+
if (!ai)
18131813
return false;
18141814

1815-
// array.uninitialized may have a first argument which is the
1816-
// allocated array buffer. The call is like a struct(buffer)
1817-
// instruction.
1818-
CGNode *arrayRefNode = conGraph->getNode(apply.getArgument(0));
1819-
if (!arrayRefNode)
1820-
return false;
1815+
auto *conGraph = getConnectionGraph(ai->getFunction());
1816+
return canOptimizeArrayUninitializedCall(ai, conGraph).isValid();
1817+
}
18211818

1822-
CGNode *arrayStructNode = conGraph->getNode(arrayStruct);
1819+
// Handle @_semantics("array.uninitialized")
1820+
//
1821+
// This call is analagous to a 'struct(storageRef)' instruction--we want a defer
1822+
// edge from the returned Array struct to the storage Reference that it
1823+
// contains.
1824+
//
1825+
// The returned unsafe pointer is handled simply by mapping the pointer value
1826+
// onto the object node that the storage argument points to.
1827+
void EscapeAnalysis::createArrayUninitializedSubgraph(
1828+
ArrayUninitCall call, ConnectionGraph *conGraph) {
1829+
CGNode *arrayStructNode = conGraph->getNode(call.arrayStruct);
18231830
assert(arrayStructNode && "Array struct must have a node");
18241831

1825-
CGNode *arrayObjNode = conGraph->getValueContent(apply.getArgument(0));
1832+
CGNode *arrayRefNode = conGraph->getNode(call.arrayStorageRef);
1833+
assert(arrayRefNode && "canOptimizeArrayUninitializedCall checks isPointer");
1834+
// If the arrayRefNode != null then arrayObjNode must be valid.
1835+
CGNode *arrayObjNode = conGraph->getValueContent(call.arrayStorageRef);
18261836

18271837
// The reference argument is effectively stored inside the returned
1828-
// array struct.
1838+
// array struct. This is like struct(arrayRefNode).
18291839
conGraph->defer(arrayStructNode, arrayRefNode);
18301840

18311841
// Map the returned element pointer to the array object's field pointer.
1832-
conGraph->setNode(arrayElementPtr, arrayObjNode);
1833-
return true;
1842+
conGraph->setNode(call.arrayElementPtr, arrayObjNode);
18341843
}
18351844

18361845
void EscapeAnalysis::analyzeInstruction(SILInstruction *I,
@@ -1852,10 +1861,15 @@ void EscapeAnalysis::analyzeInstruction(SILInstruction *I,
18521861
case ArrayCallKind::kMakeMutable:
18531862
// These array semantics calls do not capture anything.
18541863
return;
1855-
case ArrayCallKind::kArrayUninitialized:
1856-
if (createArrayUninitializedSubgraph(FAS, ConGraph))
1864+
case ArrayCallKind::kArrayUninitialized: {
1865+
ArrayUninitCall call = canOptimizeArrayUninitializedCall(
1866+
cast<ApplyInst>(FAS.getInstruction()), ConGraph);
1867+
if (call.isValid()) {
1868+
createArrayUninitializedSubgraph(call, ConGraph);
18571869
return;
1870+
}
18581871
break;
1872+
}
18591873
case ArrayCallKind::kGetElement:
18601874
if (CGNode *ArrayObjNode = ConGraph->getValueContent(ASC.getSelf())) {
18611875
CGNode *LoadedElement = nullptr;
@@ -2204,13 +2218,9 @@ void EscapeAnalysis::analyzeInstruction(SILInstruction *I,
22042218
case SILInstructionKind::TupleExtractInst: {
22052219
// This is a tuple_extract which extracts the second result of an
22062220
// array.uninitialized call (otherwise getPointerBase should have already
2207-
// looked through it). The first result is the array itself. The second
2208-
// result (which is a pointer to the array elements) must be the content
2209-
// node of the first result. It's just like a ref_element_addr
2210-
// instruction. It is mapped to a node when processing
2211-
// array.uninitialized.
2221+
// looked through it).
22122222
auto *TEI = cast<TupleExtractInst>(I);
2213-
assert(isExtractOfArrayUninitializedPointer(TEI)
2223+
assert(canOptimizeArrayUninitializedResult(TEI)
22142224
&& "tuple_extract should be handled as projection");
22152225
return;
22162226
}

test/SILOptimizer/escape_analysis.sil

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1817,3 +1817,47 @@ bb0(%0 : $IntWrapper):
18171817
%tuple = tuple (%bridge : $Builtin.BridgeObject, %ump : $UnsafeMutablePointer<Int64>)
18181818
return %tuple : $(Builtin.BridgeObject, UnsafeMutablePointer<Int64>)
18191819
}
1820+
1821+
// =============================================================================
1822+
// Test call to array.uninitialized that has extra release_value uses
1823+
1824+
class DummyArrayStorage<Element> {
1825+
@_hasStorage var count: Int { get }
1826+
@_hasStorage var capacity: Int { get }
1827+
init()
1828+
}
1829+
1830+
// init_any_array_with_buffer
1831+
sil [_semantics "array.uninitialized"] @init_any_array_with_buffer : $@convention(thin) (@owned DummyArrayStorage<AnyObject>, Int32, @thin Array<AnyObject>.Type) -> (@owned Array<AnyObject>, UnsafeMutablePointer<AnyObject>)
1832+
1833+
// CHECK-LABEL: CG of testBadArrayUninit
1834+
// CHECK-NEXT: Val [ref] %2 Esc: , Succ: (%2.1)
1835+
// CHECK-NEXT: Con [int] %2.1 Esc: G, Succ: (%2.2)
1836+
// CHECK-NEXT: Con %2.2 Esc: G, Succ:
1837+
// CHECK-NEXT: Val %5 Esc: , Succ: (%5.1)
1838+
// CHECK-NEXT: Con %5.1 Esc: G, Succ: %10
1839+
// CHECK-NEXT: Val [ref] %10 Esc: G, Succ: (%10.1)
1840+
// CHECK-NEXT: Con %10.1 Esc: G, Succ:
1841+
// CHECK-LABEL: End
1842+
sil hidden @testBadArrayUninit : $@convention(thin) (Builtin.Word, Int32) -> () {
1843+
bb0(%0 : $Builtin.Word, %1 : $Int32):
1844+
// create an array
1845+
%2 = alloc_ref [tail_elems $AnyObject * %0 : $Builtin.Word] $DummyArrayStorage<AnyObject>
1846+
%3 = metatype $@thin Array<AnyObject>.Type
1847+
%4 = function_ref @init_any_array_with_buffer : $@convention(thin) (@owned DummyArrayStorage<AnyObject>, Int32, @thin Array<AnyObject>.Type) -> (@owned Array<AnyObject>, UnsafeMutablePointer<AnyObject>)
1848+
%5 = apply %4(%2, %1, %3) : $@convention(thin) (@owned DummyArrayStorage<AnyObject>, Int32, @thin Array<AnyObject>.Type) -> (@owned Array<AnyObject>, UnsafeMutablePointer<AnyObject>)
1849+
%6 = tuple_extract %5 : $(Array<AnyObject>, UnsafeMutablePointer<AnyObject>), 0
1850+
%7 = tuple_extract %5 : $(Array<AnyObject>, UnsafeMutablePointer<AnyObject>), 1
1851+
%8 = struct_extract %7 : $UnsafeMutablePointer<AnyObject>, #UnsafeMutablePointer._rawValue
1852+
%9 = pointer_to_address %8 : $Builtin.RawPointer to [strict] $*AnyObject
1853+
1854+
// store an elt
1855+
%10 = alloc_ref $C
1856+
%11 = init_existential_ref %10 : $C : $C, $AnyObject
1857+
store %11 to %9 : $*AnyObject
1858+
1859+
// extra use of the call
1860+
release_value %5 : $(Array<AnyObject>, UnsafeMutablePointer<AnyObject>) // id: %228
1861+
%13 = tuple ()
1862+
return %13 : $()
1863+
}

0 commit comments

Comments
 (0)