mirror of
https://github.com/apple/swift.git
synced 2025-12-21 12:14:44 +01:00
Merge pull request #28917 from atrick/fix-escape
Fix a crash in StackPromotion; case not handled in EscapeAnalysis.
This commit is contained in:
@@ -1009,12 +1009,12 @@ private:
|
|||||||
/// If EscapeAnalysis should consider the given value to be a derived address
|
/// If EscapeAnalysis should consider the given value to be a derived address
|
||||||
/// or pointer based on one of its address or pointer operands, then return
|
/// or pointer based on one of its address or pointer operands, then return
|
||||||
/// that operand value. Otherwise, return an invalid value.
|
/// that operand value. Otherwise, return an invalid value.
|
||||||
SILValue getPointerBase(SILValue value) const;
|
SILValue getPointerBase(SILValue value);
|
||||||
|
|
||||||
/// Recursively find the given value's pointer base. If the value cannot be
|
/// Recursively find the given value's pointer base. If the value cannot be
|
||||||
/// represented in EscapeAnalysis as one of its operands, then return the same
|
/// represented in EscapeAnalysis as one of its operands, then return the same
|
||||||
/// value.
|
/// value.
|
||||||
SILValue getPointerRoot(SILValue value) const;
|
SILValue getPointerRoot(SILValue value);
|
||||||
|
|
||||||
PointerKind findRecursivePointerKind(SILType Ty, const SILFunction &F) const;
|
PointerKind findRecursivePointerKind(SILType Ty, const SILFunction &F) const;
|
||||||
|
|
||||||
@@ -1055,7 +1055,31 @@ private:
|
|||||||
void buildConnectionGraph(FunctionInfo *FInfo, FunctionOrder &BottomUpOrder,
|
void buildConnectionGraph(FunctionInfo *FInfo, FunctionOrder &BottomUpOrder,
|
||||||
int RecursionDepth);
|
int RecursionDepth);
|
||||||
|
|
||||||
bool createArrayUninitializedSubgraph(FullApplySite apply,
|
// @_semantics("array.uninitialized") takes a reference to the storage and
|
||||||
|
// returns an instantiated array struct and unsafe pointer to the elements.
|
||||||
|
struct ArrayUninitCall {
|
||||||
|
SILValue arrayStorageRef;
|
||||||
|
TupleExtractInst *arrayStruct = nullptr;
|
||||||
|
TupleExtractInst *arrayElementPtr = nullptr;
|
||||||
|
|
||||||
|
bool isValid() const {
|
||||||
|
return arrayStorageRef && arrayStruct && arrayElementPtr;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/// If \p ai is an optimizable @_semantics("array.uninitialized") call, return
|
||||||
|
/// valid call information.
|
||||||
|
ArrayUninitCall canOptimizeArrayUninitializedCall(ApplyInst *ai,
|
||||||
|
ConnectionGraph *conGraph);
|
||||||
|
|
||||||
|
/// Return true of this tuple_extract is the result of an optimizable
|
||||||
|
/// @_semantics("array.uninitialized") call.
|
||||||
|
bool canOptimizeArrayUninitializedResult(TupleExtractInst *tei);
|
||||||
|
|
||||||
|
/// Handle a call to "@_semantics(array.uninitialized") precisely by mapping
|
||||||
|
/// each call result to a separate graph node and relating them to the
|
||||||
|
/// argument.
|
||||||
|
void createArrayUninitializedSubgraph(ArrayUninitCall call,
|
||||||
ConnectionGraph *conGraph);
|
ConnectionGraph *conGraph);
|
||||||
|
|
||||||
/// Updates the graph by analyzing instruction \p I.
|
/// Updates the graph by analyzing instruction \p I.
|
||||||
|
|||||||
@@ -110,18 +110,10 @@ EscapeAnalysis::findCachedPointerKind(SILType Ty, const SILFunction &F) const {
|
|||||||
return pointerKind;
|
return pointerKind;
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool isExtractOfArrayUninitializedPointer(TupleExtractInst *TEI) {
|
|
||||||
if (auto apply = dyn_cast<ApplyInst>(TEI->getOperand()))
|
|
||||||
if (ArraySemanticsCall(apply, "array.uninitialized", false))
|
|
||||||
return true;
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// If EscapeAnalysis should consider the given value to be a derived address or
|
// If EscapeAnalysis should consider the given value to be a derived address or
|
||||||
// pointer based on one of its address or pointer operands, then return that
|
// pointer based on one of its address or pointer operands, then return that
|
||||||
// operand value. Otherwise, return an invalid value.
|
// operand value. Otherwise, return an invalid value.
|
||||||
SILValue EscapeAnalysis::getPointerBase(SILValue value) const {
|
SILValue EscapeAnalysis::getPointerBase(SILValue value) {
|
||||||
switch (value->getKind()) {
|
switch (value->getKind()) {
|
||||||
case ValueKind::IndexAddrInst:
|
case ValueKind::IndexAddrInst:
|
||||||
case ValueKind::IndexRawPointerInst:
|
case ValueKind::IndexRawPointerInst:
|
||||||
@@ -156,10 +148,8 @@ SILValue EscapeAnalysis::getPointerBase(SILValue value) const {
|
|||||||
case ValueKind::TupleExtractInst: {
|
case ValueKind::TupleExtractInst: {
|
||||||
auto *TEI = cast<TupleExtractInst>(value);
|
auto *TEI = cast<TupleExtractInst>(value);
|
||||||
// Special handling for extracting the pointer-result from an
|
// Special handling for extracting the pointer-result from an
|
||||||
// array construction. We handle this like a ref_element_addr
|
// array construction. See createArrayUninitializedSubgraph.
|
||||||
// rather than a projection. See the handling of tuple_extract
|
if (canOptimizeArrayUninitializedResult(TEI))
|
||||||
// in analyzeInstruction().
|
|
||||||
if (isExtractOfArrayUninitializedPointer(TEI))
|
|
||||||
return SILValue();
|
return SILValue();
|
||||||
return TEI->getOperand();
|
return TEI->getOperand();
|
||||||
}
|
}
|
||||||
@@ -188,7 +178,7 @@ SILValue EscapeAnalysis::getPointerBase(SILValue value) const {
|
|||||||
// Recursively find the given value's pointer base. If the value cannot be
|
// Recursively find the given value's pointer base. If the value cannot be
|
||||||
// represented in EscapeAnalysis as one of its operands, then return the same
|
// represented in EscapeAnalysis as one of its operands, then return the same
|
||||||
// value.
|
// value.
|
||||||
SILValue EscapeAnalysis::getPointerRoot(SILValue value) const {
|
SILValue EscapeAnalysis::getPointerRoot(SILValue value) {
|
||||||
while (true) {
|
while (true) {
|
||||||
if (SILValue v2 = getPointerBase(value))
|
if (SILValue v2 = getPointerBase(value))
|
||||||
value = v2;
|
value = v2;
|
||||||
@@ -1721,28 +1711,6 @@ void EscapeAnalysis::buildConnectionGraph(FunctionInfo *FInfo,
|
|||||||
<< FInfo->Graph.F->getName() << '\n');
|
<< FInfo->Graph.F->getName() << '\n');
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the tuple extract for the first two fields if all uses of \p I are
|
|
||||||
/// tuple_extract instructions.
|
|
||||||
static std::pair<TupleExtractInst *, TupleExtractInst *>
|
|
||||||
onlyUsedInTupleExtract(SILValue V) {
|
|
||||||
TupleExtractInst *field0 = nullptr;
|
|
||||||
TupleExtractInst *field1 = nullptr;
|
|
||||||
for (Operand *Use : getNonDebugUses(V)) {
|
|
||||||
if (auto *TEI = dyn_cast<TupleExtractInst>(Use->getUser())) {
|
|
||||||
if (TEI->getFieldNo() == 0) {
|
|
||||||
field0 = TEI;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (TEI->getFieldNo() == 1) {
|
|
||||||
field1 = TEI;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return std::make_pair(nullptr, nullptr);
|
|
||||||
}
|
|
||||||
return std::make_pair(field0, field1);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool EscapeAnalysis::buildConnectionGraphForCallees(
|
bool EscapeAnalysis::buildConnectionGraphForCallees(
|
||||||
SILInstruction *Caller, CalleeList Callees, FunctionInfo *FInfo,
|
SILInstruction *Caller, CalleeList Callees, FunctionInfo *FInfo,
|
||||||
FunctionOrder &BottomUpOrder, int RecursionDepth) {
|
FunctionOrder &BottomUpOrder, int RecursionDepth) {
|
||||||
@@ -1799,38 +1767,79 @@ bool EscapeAnalysis::buildConnectionGraphForDestructor(
|
|||||||
RecursionDepth);
|
RecursionDepth);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle array.uninitialized
|
EscapeAnalysis::ArrayUninitCall
|
||||||
bool EscapeAnalysis::createArrayUninitializedSubgraph(
|
EscapeAnalysis::canOptimizeArrayUninitializedCall(ApplyInst *ai,
|
||||||
FullApplySite apply, ConnectionGraph *conGraph) {
|
ConnectionGraph *conGraph) {
|
||||||
|
ArrayUninitCall call;
|
||||||
|
// This must be an exact match so we don't accidentally optimize
|
||||||
|
// "array.uninitialized_intrinsic".
|
||||||
|
if (!ArraySemanticsCall(ai, "array.uninitialized", false))
|
||||||
|
return call;
|
||||||
|
|
||||||
// Check if the result is used in the usual way: extracting the
|
// Check if the result is used in the usual way: extracting the
|
||||||
// array and the element pointer with tuple_extract.
|
// array and the element pointer with tuple_extract.
|
||||||
TupleExtractInst *arrayStruct;
|
for (Operand *use : getNonDebugUses(ai)) {
|
||||||
TupleExtractInst *arrayElementPtr;
|
if (auto *tei = dyn_cast<TupleExtractInst>(use->getUser())) {
|
||||||
std::tie(arrayStruct, arrayElementPtr) =
|
if (tei->getFieldNo() == 0) {
|
||||||
onlyUsedInTupleExtract(cast<ApplyInst>(apply.getInstruction()));
|
call.arrayStruct = tei;
|
||||||
if (!arrayStruct || !arrayElementPtr)
|
continue;
|
||||||
|
}
|
||||||
|
if (tei->getFieldNo() == 1) {
|
||||||
|
call.arrayElementPtr = tei;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// If there are any other uses, such as a release_value, erase the previous
|
||||||
|
// call info and bail out.
|
||||||
|
call.arrayStruct = nullptr;
|
||||||
|
call.arrayElementPtr = nullptr;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
// An "array.uninitialized" call may have a first argument which is the
|
||||||
|
// allocated array buffer. Make sure the call's argument is recognized by
|
||||||
|
// EscapeAnalysis as a pointer, otherwise createArrayUninitializedSubgraph
|
||||||
|
// won't be able to map the result nodes onto it. There is a variant of
|
||||||
|
// @_semantics("array.uninitialized") that does not take the storage as input,
|
||||||
|
// so it will effectively bail out here.
|
||||||
|
if (isPointer(ai->getArgument(0)))
|
||||||
|
call.arrayStorageRef = ai->getArgument(0);
|
||||||
|
return call;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool EscapeAnalysis::canOptimizeArrayUninitializedResult(
|
||||||
|
TupleExtractInst *tei) {
|
||||||
|
ApplyInst *ai = dyn_cast<ApplyInst>(tei->getOperand());
|
||||||
|
if (!ai)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
// array.uninitialized may have a first argument which is the
|
auto *conGraph = getConnectionGraph(ai->getFunction());
|
||||||
// allocated array buffer. The call is like a struct(buffer)
|
return canOptimizeArrayUninitializedCall(ai, conGraph).isValid();
|
||||||
// instruction.
|
}
|
||||||
CGNode *arrayRefNode = conGraph->getNode(apply.getArgument(0));
|
|
||||||
if (!arrayRefNode)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
CGNode *arrayStructNode = conGraph->getNode(arrayStruct);
|
// Handle @_semantics("array.uninitialized")
|
||||||
|
//
|
||||||
|
// This call is analagous to a 'struct(storageRef)' instruction--we want a defer
|
||||||
|
// edge from the returned Array struct to the storage Reference that it
|
||||||
|
// contains.
|
||||||
|
//
|
||||||
|
// The returned unsafe pointer is handled simply by mapping the pointer value
|
||||||
|
// onto the object node that the storage argument points to.
|
||||||
|
void EscapeAnalysis::createArrayUninitializedSubgraph(
|
||||||
|
ArrayUninitCall call, ConnectionGraph *conGraph) {
|
||||||
|
CGNode *arrayStructNode = conGraph->getNode(call.arrayStruct);
|
||||||
assert(arrayStructNode && "Array struct must have a node");
|
assert(arrayStructNode && "Array struct must have a node");
|
||||||
|
|
||||||
CGNode *arrayObjNode = conGraph->getValueContent(apply.getArgument(0));
|
CGNode *arrayRefNode = conGraph->getNode(call.arrayStorageRef);
|
||||||
|
assert(arrayRefNode && "canOptimizeArrayUninitializedCall checks isPointer");
|
||||||
|
// If the arrayRefNode != null then arrayObjNode must be valid.
|
||||||
|
CGNode *arrayObjNode = conGraph->getValueContent(call.arrayStorageRef);
|
||||||
|
|
||||||
// The reference argument is effectively stored inside the returned
|
// The reference argument is effectively stored inside the returned
|
||||||
// array struct.
|
// array struct. This is like struct(arrayRefNode).
|
||||||
conGraph->defer(arrayStructNode, arrayRefNode);
|
conGraph->defer(arrayStructNode, arrayRefNode);
|
||||||
|
|
||||||
// Map the returned element pointer to the array object's field pointer.
|
// Map the returned element pointer to the array object's field pointer.
|
||||||
conGraph->setNode(arrayElementPtr, arrayObjNode);
|
conGraph->setNode(call.arrayElementPtr, arrayObjNode);
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void EscapeAnalysis::analyzeInstruction(SILInstruction *I,
|
void EscapeAnalysis::analyzeInstruction(SILInstruction *I,
|
||||||
@@ -1852,10 +1861,15 @@ void EscapeAnalysis::analyzeInstruction(SILInstruction *I,
|
|||||||
case ArrayCallKind::kMakeMutable:
|
case ArrayCallKind::kMakeMutable:
|
||||||
// These array semantics calls do not capture anything.
|
// These array semantics calls do not capture anything.
|
||||||
return;
|
return;
|
||||||
case ArrayCallKind::kArrayUninitialized:
|
case ArrayCallKind::kArrayUninitialized: {
|
||||||
if (createArrayUninitializedSubgraph(FAS, ConGraph))
|
ArrayUninitCall call = canOptimizeArrayUninitializedCall(
|
||||||
|
cast<ApplyInst>(FAS.getInstruction()), ConGraph);
|
||||||
|
if (call.isValid()) {
|
||||||
|
createArrayUninitializedSubgraph(call, ConGraph);
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
case ArrayCallKind::kGetElement:
|
case ArrayCallKind::kGetElement:
|
||||||
if (CGNode *ArrayObjNode = ConGraph->getValueContent(ASC.getSelf())) {
|
if (CGNode *ArrayObjNode = ConGraph->getValueContent(ASC.getSelf())) {
|
||||||
CGNode *LoadedElement = nullptr;
|
CGNode *LoadedElement = nullptr;
|
||||||
@@ -2204,13 +2218,9 @@ void EscapeAnalysis::analyzeInstruction(SILInstruction *I,
|
|||||||
case SILInstructionKind::TupleExtractInst: {
|
case SILInstructionKind::TupleExtractInst: {
|
||||||
// This is a tuple_extract which extracts the second result of an
|
// This is a tuple_extract which extracts the second result of an
|
||||||
// array.uninitialized call (otherwise getPointerBase should have already
|
// array.uninitialized call (otherwise getPointerBase should have already
|
||||||
// looked through it). The first result is the array itself. The second
|
// looked through it).
|
||||||
// result (which is a pointer to the array elements) must be the content
|
|
||||||
// node of the first result. It's just like a ref_element_addr
|
|
||||||
// instruction. It is mapped to a node when processing
|
|
||||||
// array.uninitialized.
|
|
||||||
auto *TEI = cast<TupleExtractInst>(I);
|
auto *TEI = cast<TupleExtractInst>(I);
|
||||||
assert(isExtractOfArrayUninitializedPointer(TEI)
|
assert(canOptimizeArrayUninitializedResult(TEI)
|
||||||
&& "tuple_extract should be handled as projection");
|
&& "tuple_extract should be handled as projection");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1817,3 +1817,47 @@ bb0(%0 : $IntWrapper):
|
|||||||
%tuple = tuple (%bridge : $Builtin.BridgeObject, %ump : $UnsafeMutablePointer<Int64>)
|
%tuple = tuple (%bridge : $Builtin.BridgeObject, %ump : $UnsafeMutablePointer<Int64>)
|
||||||
return %tuple : $(Builtin.BridgeObject, UnsafeMutablePointer<Int64>)
|
return %tuple : $(Builtin.BridgeObject, UnsafeMutablePointer<Int64>)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// Test call to array.uninitialized that has extra release_value uses
|
||||||
|
|
||||||
|
class DummyArrayStorage<Element> {
|
||||||
|
@_hasStorage var count: Int { get }
|
||||||
|
@_hasStorage var capacity: Int { get }
|
||||||
|
init()
|
||||||
|
}
|
||||||
|
|
||||||
|
// init_any_array_with_buffer
|
||||||
|
sil [_semantics "array.uninitialized"] @init_any_array_with_buffer : $@convention(thin) (@owned DummyArrayStorage<AnyObject>, Int32, @thin Array<AnyObject>.Type) -> (@owned Array<AnyObject>, UnsafeMutablePointer<AnyObject>)
|
||||||
|
|
||||||
|
// CHECK-LABEL: CG of testBadArrayUninit
|
||||||
|
// CHECK-NEXT: Val [ref] %2 Esc: , Succ: (%2.1)
|
||||||
|
// CHECK-NEXT: Con [int] %2.1 Esc: G, Succ: (%2.2)
|
||||||
|
// CHECK-NEXT: Con %2.2 Esc: G, Succ:
|
||||||
|
// CHECK-NEXT: Val %5 Esc: , Succ: (%5.1)
|
||||||
|
// CHECK-NEXT: Con %5.1 Esc: G, Succ: %10
|
||||||
|
// CHECK-NEXT: Val [ref] %10 Esc: G, Succ: (%10.1)
|
||||||
|
// CHECK-NEXT: Con %10.1 Esc: G, Succ:
|
||||||
|
// CHECK-LABEL: End
|
||||||
|
sil hidden @testBadArrayUninit : $@convention(thin) (Builtin.Word, Int32) -> () {
|
||||||
|
bb0(%0 : $Builtin.Word, %1 : $Int32):
|
||||||
|
// create an array
|
||||||
|
%2 = alloc_ref [tail_elems $AnyObject * %0 : $Builtin.Word] $DummyArrayStorage<AnyObject>
|
||||||
|
%3 = metatype $@thin Array<AnyObject>.Type
|
||||||
|
%4 = function_ref @init_any_array_with_buffer : $@convention(thin) (@owned DummyArrayStorage<AnyObject>, Int32, @thin Array<AnyObject>.Type) -> (@owned Array<AnyObject>, UnsafeMutablePointer<AnyObject>)
|
||||||
|
%5 = apply %4(%2, %1, %3) : $@convention(thin) (@owned DummyArrayStorage<AnyObject>, Int32, @thin Array<AnyObject>.Type) -> (@owned Array<AnyObject>, UnsafeMutablePointer<AnyObject>)
|
||||||
|
%6 = tuple_extract %5 : $(Array<AnyObject>, UnsafeMutablePointer<AnyObject>), 0
|
||||||
|
%7 = tuple_extract %5 : $(Array<AnyObject>, UnsafeMutablePointer<AnyObject>), 1
|
||||||
|
%8 = struct_extract %7 : $UnsafeMutablePointer<AnyObject>, #UnsafeMutablePointer._rawValue
|
||||||
|
%9 = pointer_to_address %8 : $Builtin.RawPointer to [strict] $*AnyObject
|
||||||
|
|
||||||
|
// store an elt
|
||||||
|
%10 = alloc_ref $C
|
||||||
|
%11 = init_existential_ref %10 : $C : $C, $AnyObject
|
||||||
|
store %11 to %9 : $*AnyObject
|
||||||
|
|
||||||
|
// extra use of the call
|
||||||
|
release_value %5 : $(Array<AnyObject>, UnsafeMutablePointer<AnyObject>) // id: %228
|
||||||
|
%13 = tuple ()
|
||||||
|
return %13 : $()
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user