mirror of
https://github.com/apple/swift.git
synced 2025-12-21 12:14:44 +01:00
stdlib, SIL optimizer: use the SIL copy-on-write representation in the Array types.
Use the new builtins for COW representation in Array, ContiguousArray and ArraySlice. The basic idea is to strictly separate code which mutates an array buffer from code which reads from an array. The concept is explained in more detail in docs/SIL.rst, section "Copy-on-Write Representation". The main change is to use beginCOWMutation() instead of isUniquelyReferenced() and insert endCOWMutation() at the end of all mutating functions. Also, reading from the array buffer must be done differently, depending on if the buffer is in a mutable or immutable state. All the required invariants are enforced by runtime checks - but only in an assert-build of the library: a bit in the buffer object side-table indicates if the buffer is mutable or not. Along with the library changes, also two optimizations needed to be updated: COWArrayOpt and ObjectOutliner.
This commit is contained in:
@@ -35,17 +35,18 @@ class ObjectOutliner {
|
||||
return type.getNominalOrBoundGenericNominal() == ArrayDecl;
|
||||
}
|
||||
|
||||
bool isValidUseOfObject(SILInstruction *Val,
|
||||
bool isCOWObject,
|
||||
ApplyInst **FindStringCall = nullptr);
|
||||
bool isValidUseOfObject(SILInstruction *Val, EndCOWMutationInst *toIgnore);
|
||||
|
||||
ApplyInst *findFindStringCall(SILValue V);
|
||||
|
||||
bool getObjectInitVals(SILValue Val,
|
||||
llvm::DenseMap<VarDecl *, StoreInst *> &MemberStores,
|
||||
llvm::SmallVectorImpl<StoreInst *> &TailStores,
|
||||
unsigned NumTailTupleElements,
|
||||
ApplyInst **FindStringCall);
|
||||
EndCOWMutationInst *toIgnore);
|
||||
bool handleTailAddr(int TailIdx, SILInstruction *I, unsigned NumTailTupleElements,
|
||||
llvm::SmallVectorImpl<StoreInst *> &TailStores);
|
||||
llvm::SmallVectorImpl<StoreInst *> &TailStores,
|
||||
EndCOWMutationInst *toIgnore);
|
||||
|
||||
bool optimizeObjectAllocation(AllocRefInst *ARI);
|
||||
void replaceFindStringCall(ApplyInst *FindStringCall);
|
||||
@@ -116,13 +117,11 @@ static bool isValidInitVal(SILValue V) {
|
||||
}
|
||||
|
||||
/// Check if a use of an object may prevent outlining the object.
|
||||
///
|
||||
/// If \p isCOWObject is true, then the object reference is wrapped into a
|
||||
/// COW container. Currently this is just Array<T>.
|
||||
/// If a use is a call to the findStringSwitchCase semantic call, the apply
|
||||
/// is returned in \p FindStringCall.
|
||||
bool ObjectOutliner::isValidUseOfObject(SILInstruction *I, bool isCOWObject,
|
||||
ApplyInst **FindStringCall) {
|
||||
bool ObjectOutliner::isValidUseOfObject(SILInstruction *I,
|
||||
EndCOWMutationInst *toIgnore) {
|
||||
if (I == toIgnore)
|
||||
return true;
|
||||
|
||||
switch (I->getKind()) {
|
||||
case SILInstructionKind::DebugValueAddrInst:
|
||||
case SILInstructionKind::DebugValueInst:
|
||||
@@ -134,49 +133,22 @@ bool ObjectOutliner::isValidUseOfObject(SILInstruction *I, bool isCOWObject,
|
||||
case SILInstructionKind::SetDeallocatingInst:
|
||||
return true;
|
||||
|
||||
case SILInstructionKind::ReturnInst:
|
||||
case SILInstructionKind::TryApplyInst:
|
||||
case SILInstructionKind::PartialApplyInst:
|
||||
case SILInstructionKind::StoreInst:
|
||||
/// We don't have a representation for COW objects in SIL, so we do some
|
||||
/// ad-hoc testing: We can ignore uses of a COW object if any use after
|
||||
/// this will do a uniqueness checking before the object is modified.
|
||||
return isCOWObject;
|
||||
|
||||
case SILInstructionKind::ApplyInst:
|
||||
if (!isCOWObject)
|
||||
return false;
|
||||
// There should only be a single call to findStringSwitchCase. But even
|
||||
// if there are multiple calls, it's not problem - we'll just optimize the
|
||||
// last one we find.
|
||||
if (cast<ApplyInst>(I)->hasSemantics(semantics::FIND_STRING_SWITCH_CASE))
|
||||
*FindStringCall = cast<ApplyInst>(I);
|
||||
return true;
|
||||
|
||||
case SILInstructionKind::StructInst:
|
||||
if (isCOWType(cast<StructInst>(I)->getType())) {
|
||||
// The object is wrapped into a COW container.
|
||||
isCOWObject = true;
|
||||
}
|
||||
break;
|
||||
|
||||
case SILInstructionKind::UncheckedRefCastInst:
|
||||
case SILInstructionKind::StructElementAddrInst:
|
||||
case SILInstructionKind::AddressToPointerInst:
|
||||
assert(!isCOWObject && "instruction cannot have a COW object as operand");
|
||||
break;
|
||||
|
||||
case SILInstructionKind::StructInst:
|
||||
case SILInstructionKind::TupleInst:
|
||||
case SILInstructionKind::TupleExtractInst:
|
||||
case SILInstructionKind::EnumInst:
|
||||
break;
|
||||
|
||||
case SILInstructionKind::StructExtractInst:
|
||||
// To be on the safe side we don't consider the object as COW if it is
|
||||
// extracted again from the COW container: the uniqueness check may be
|
||||
// optimized away in this case.
|
||||
isCOWObject = false;
|
||||
break;
|
||||
case SILInstructionKind::UncheckedRefCastInst:
|
||||
case SILInstructionKind::UpcastInst: {
|
||||
auto SVI = cast<SingleValueInstruction>(I);
|
||||
for (Operand *Use : getNonDebugUses(SVI)) {
|
||||
if (!isValidUseOfObject(Use->getUser(), toIgnore))
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
case SILInstructionKind::BuiltinInst: {
|
||||
// Handle the case for comparing addresses. This occurs when the Array
|
||||
@@ -198,26 +170,51 @@ bool ObjectOutliner::isValidUseOfObject(SILInstruction *I, bool isCOWObject,
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
auto SVI = cast<SingleValueInstruction>(I);
|
||||
for (Operand *Use : getNonDebugUses(SVI)) {
|
||||
if (!isValidUseOfObject(Use->getUser(), isCOWObject, FindStringCall))
|
||||
return false;
|
||||
/// Finds a call to findStringSwitchCase in the uses of \p V.
|
||||
ApplyInst *ObjectOutliner::findFindStringCall(SILValue V) {
|
||||
for (Operand *use : V->getUses()) {
|
||||
SILInstruction *user = use->getUser();
|
||||
switch (user->getKind()) {
|
||||
case SILInstructionKind::ApplyInst:
|
||||
// There should only be a single call to findStringSwitchCase. But even
|
||||
// if there are multiple calls, it's not problem - we'll just optimize the
|
||||
// last one we find.
|
||||
if (cast<ApplyInst>(user)->hasSemantics(semantics::FIND_STRING_SWITCH_CASE))
|
||||
return cast<ApplyInst>(user);
|
||||
break;
|
||||
|
||||
case SILInstructionKind::StructInst:
|
||||
case SILInstructionKind::TupleInst:
|
||||
case SILInstructionKind::UncheckedRefCastInst:
|
||||
case SILInstructionKind::UpcastInst: {
|
||||
if (ApplyInst *foundCall =
|
||||
findFindStringCall(cast<SingleValueInstruction>(user))) {
|
||||
return foundCall;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
/// Handle the address of a tail element.
|
||||
bool ObjectOutliner::handleTailAddr(int TailIdx, SILInstruction *TailAddr,
|
||||
unsigned NumTailTupleElements,
|
||||
llvm::SmallVectorImpl<StoreInst *> &TailStores) {
|
||||
llvm::SmallVectorImpl<StoreInst *> &TailStores,
|
||||
EndCOWMutationInst *toIgnore) {
|
||||
if (NumTailTupleElements > 0) {
|
||||
if (auto *TEA = dyn_cast<TupleElementAddrInst>(TailAddr)) {
|
||||
unsigned TupleIdx = TEA->getFieldNo();
|
||||
assert(TupleIdx < NumTailTupleElements);
|
||||
for (Operand *Use : TEA->getUses()) {
|
||||
if (!handleTailAddr(TailIdx * NumTailTupleElements + TupleIdx, Use->getUser(), 0,
|
||||
TailStores))
|
||||
TailStores, toIgnore))
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
@@ -232,7 +229,7 @@ bool ObjectOutliner::handleTailAddr(int TailIdx, SILInstruction *TailAddr,
|
||||
}
|
||||
}
|
||||
}
|
||||
return isValidUseOfObject(TailAddr, /*isCOWObject*/false);
|
||||
return isValidUseOfObject(TailAddr, toIgnore);
|
||||
}
|
||||
|
||||
/// Get the init values for an object's stored properties and its tail elements.
|
||||
@@ -240,12 +237,13 @@ bool ObjectOutliner::getObjectInitVals(SILValue Val,
|
||||
llvm::DenseMap<VarDecl *, StoreInst *> &MemberStores,
|
||||
llvm::SmallVectorImpl<StoreInst *> &TailStores,
|
||||
unsigned NumTailTupleElements,
|
||||
ApplyInst **FindStringCall) {
|
||||
EndCOWMutationInst *toIgnore) {
|
||||
for (Operand *Use : Val->getUses()) {
|
||||
SILInstruction *User = Use->getUser();
|
||||
if (auto *UC = dyn_cast<UpcastInst>(User)) {
|
||||
// Upcast is transparent.
|
||||
if (!getObjectInitVals(UC, MemberStores, TailStores, NumTailTupleElements, FindStringCall))
|
||||
if (!getObjectInitVals(UC, MemberStores, TailStores, NumTailTupleElements,
|
||||
toIgnore))
|
||||
return false;
|
||||
} else if (auto *REA = dyn_cast<RefElementAddrInst>(User)) {
|
||||
// The address of a stored property.
|
||||
@@ -255,7 +253,7 @@ bool ObjectOutliner::getObjectInitVals(SILValue Val,
|
||||
if (!isValidInitVal(SI->getSrc()) || MemberStores[REA->getField()])
|
||||
return false;
|
||||
MemberStores[REA->getField()] = SI;
|
||||
} else if (!isValidUseOfObject(ElemAddrUser, /*isCOWObject*/false)) {
|
||||
} else if (!isValidUseOfObject(ElemAddrUser, toIgnore)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -272,15 +270,17 @@ bool ObjectOutliner::getObjectInitVals(SILValue Val,
|
||||
TailIdx = Index->getValue().getZExtValue();
|
||||
|
||||
for (Operand *IAUse : IA->getUses()) {
|
||||
if (!handleTailAddr(TailIdx, IAUse->getUser(), NumTailTupleElements, TailStores))
|
||||
if (!handleTailAddr(TailIdx, IAUse->getUser(), NumTailTupleElements,
|
||||
TailStores, toIgnore))
|
||||
return false;
|
||||
}
|
||||
// Without an index_addr it's the first tail element.
|
||||
} else if (!handleTailAddr(/*TailIdx*/0, TailUser, NumTailTupleElements, TailStores)) {
|
||||
} else if (!handleTailAddr(/*TailIdx*/0, TailUser, NumTailTupleElements,
|
||||
TailStores, toIgnore)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
} else if (!isValidUseOfObject(User, /*isCOWObject*/false, FindStringCall)) {
|
||||
} else if (!isValidUseOfObject(User, toIgnore)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -302,10 +302,25 @@ public:
|
||||
}
|
||||
};
|
||||
|
||||
static EndCOWMutationInst *getEndCOWMutation(SILValue object) {
|
||||
for (Operand *use : object->getUses()) {
|
||||
SILInstruction *user = use->getUser();
|
||||
if (auto *upCast = dyn_cast<UpcastInst>(user)) {
|
||||
// Look through upcast instructions.
|
||||
if (EndCOWMutationInst *ecm = getEndCOWMutation(upCast))
|
||||
return ecm;
|
||||
} else if (auto *ecm = dyn_cast<EndCOWMutationInst>(use->getUser())) {
|
||||
return ecm;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
/// Try to convert an object allocation into a statically initialized object.
|
||||
///
|
||||
/// In general this works for any class, but in practice it will only kick in
|
||||
/// for array buffer objects. The use cases are array literals in a function.
|
||||
/// for copy-on-write buffers, like array buffer objects.
|
||||
/// The use cases are array literals in a function.
|
||||
/// For example:
|
||||
/// func getarray() -> [Int] {
|
||||
/// return [1, 2, 3]
|
||||
@@ -314,6 +329,12 @@ bool ObjectOutliner::optimizeObjectAllocation(AllocRefInst *ARI) {
|
||||
if (ARI->isObjC())
|
||||
return false;
|
||||
|
||||
// Find the end_cow_mutation. Only for such COW buffer objects we do the
|
||||
// transformation.
|
||||
EndCOWMutationInst *endCOW = getEndCOWMutation(ARI);
|
||||
if (!endCOW || endCOW->doKeepUnique())
|
||||
return false;
|
||||
|
||||
// Check how many tail allocated elements are on the object.
|
||||
ArrayRef<Operand> TailCounts = ARI->getTailAllocatedCounts();
|
||||
SILType TailType;
|
||||
@@ -363,12 +384,13 @@ bool ObjectOutliner::optimizeObjectAllocation(AllocRefInst *ARI) {
|
||||
}
|
||||
|
||||
TailStores.resize(NumStores);
|
||||
ApplyInst *FindStringCall = nullptr;
|
||||
|
||||
// Get the initialization stores of the object's properties and tail
|
||||
// allocated elements. Also check if there are any "bad" uses of the object.
|
||||
if (!getObjectInitVals(ARI, MemberStores, TailStores, NumTailTupleElems, &FindStringCall))
|
||||
if (!getObjectInitVals(ARI, MemberStores, TailStores, NumTailTupleElems,
|
||||
endCOW)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Is there a store for all the class properties?
|
||||
if (MemberStores.size() != Fields.size())
|
||||
@@ -452,6 +474,11 @@ bool ObjectOutliner::optimizeObjectAllocation(AllocRefInst *ARI) {
|
||||
SILBuilder B(ARI);
|
||||
GlobalValueInst *GVI = B.createGlobalValue(ARI->getLoc(), Glob);
|
||||
B.createStrongRetain(ARI->getLoc(), GVI, B.getDefaultAtomicity());
|
||||
|
||||
ApplyInst *FindStringCall = findFindStringCall(endCOW);
|
||||
endCOW->replaceAllUsesWith(endCOW->getOperand());
|
||||
ToRemove.push_back(endCOW);
|
||||
|
||||
llvm::SmallVector<Operand *, 8> Worklist(ARI->use_begin(), ARI->use_end());
|
||||
while (!Worklist.empty()) {
|
||||
auto *Use = Worklist.pop_back_val();
|
||||
|
||||
Reference in New Issue
Block a user