Skip to content

Commit 786c29a

Browse files
committed
Fix a crash in StackPromotion; case not handled in EscapeAnalysis.
StackPromotion queries EscapeAnalysis. The escape information for the result of an array semantic was missing because it was an ill-formed call. This fix introduces a new check to determine whether a call is well formed so it can be handled consistently everywhere. Fixes <rdar://problem/58113508> Interpreter/SDK/objc_fast_enumeration.swift failed on iphonesimulator-i386 The bug was introduced here: commit 8b926af Author: Andrew Trick <[email protected]> Date: Mon Dec 16 16:04:09 2019 -0800 EscapeAnalysis: Add PointerKind and interior/reference flags The bug can occur when a semantic "array.ininitialized" call (Array.adoptStorage) at the SIL level has direct "release_value" instructions that don't use the tuple_extract values corresponding to the call's returned value. This is part of the tragedy of not supporting multiple call results. When users of the call's result can use either the entire tuple (which is a meaningless value), or the individual tuple extracts, then there's no way to handle calls uniformly. The change that broke this was to remove the special handling of TupleExtract instructions. That special handling was identical to the way that any normal pointer projection is handled. So, as a simplification, the new code just expects that case to be handled naturally as a pointer projection. This case was already guarded by a check to determine whether the TupleExtract was actually the result of an array.uninitialized call, in which case the normal handling does not apply. However, because of the weirdness mentioned above, the handling of "array.ininitialized" may silently bail out. When that happens, the TupleExtract gets neither the special handling, nor the normal handling. Note that the old special handling of TupleExtract was bizarre and relied on corresponding weirdness in the code that handles "array.uninitialized". The new code simply creates a separate graph node for each result of the call, and creates the edges that exactly correspond to load and stores on those results. So, rather than reinstating the previous code, which I can't quite reason about, this fix creates a single check to determine whether a TupleExtract is treated as an "array.uninitialized" result or not, and use that check in both places.
1 parent 4062012 commit 786c29a

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)