Skip to content

Commit f319f15

Browse files
committed
[OSLogOptimization] Fix a bug in the replaceAllUsesAndFixLifetimes
function of the OSLogOptimization pass that happens when folding a guaranteed value with a constant (owned) value. The fix inserts the constant value at the beginning of the borrowed scope of the guaranteed value rather than at the definition of the guaranteed value. Update the SIL tests for the new folding pattern and add a test that catches this bug. Also, re-enable the OSLogPrototypeExecTest.swift that was disabled due to this bug.
1 parent 82aff15 commit f319f15

File tree

3 files changed

+142
-270
lines changed

3 files changed

+142
-270
lines changed

lib/SILOptimizer/Mandatory/OSLogOptimization.cpp

Lines changed: 71 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@
8282
#include "swift/SIL/BasicBlockUtils.h"
8383
#include "swift/SIL/CFG.h"
8484
#include "swift/SIL/InstructionUtils.h"
85+
#include "swift/SIL/OwnershipUtils.h"
8586
#include "swift/SIL/SILBasicBlock.h"
8687
#include "swift/SIL/SILBuilder.h"
8788
#include "swift/SIL/SILConstants.h"
@@ -510,7 +511,6 @@ static SILValue emitCodeForConstantArray(ArrayRef<SILValue> elements,
510511
assert(astContext.getArrayDecl() ==
511512
arrayType->getNominalOrBoundGenericNominal());
512513
SILModule &module = builder.getModule();
513-
SILFunction &fun = builder.getFunction();
514514

515515
// Create a SILValue for the number of elements.
516516
unsigned numElements = elements.size();
@@ -538,19 +538,10 @@ static SILValue emitCodeForConstantArray(ArrayRef<SILValue> elements,
538538
loc, arrayAllocateRef, subMap, ArrayRef<SILValue>(numElementsSIL), false);
539539

540540
// Extract the elements of the tuple returned by the call to the allocator.
541-
SILValue arraySIL;
542-
SILValue storagePointerSIL;
543-
if (fun.hasOwnership()) {
544-
DestructureTupleInst *destructureInst =
545-
builder.createDestructureTuple(loc, applyInst);
546-
arraySIL = destructureInst->getResults()[0];
547-
storagePointerSIL = destructureInst->getResults()[1];
548-
} else {
549-
SILType arraySILType = SILType::getPrimitiveObjectType(arrayType);
550-
arraySIL = builder.createTupleExtract(loc, applyInst, 0, arraySILType);
551-
storagePointerSIL = builder.createTupleExtract(
552-
loc, applyInst, 1, SILType::getRawPointerType(astContext));
553-
}
541+
DestructureTupleInst *destructureInst =
542+
builder.createDestructureTuple(loc, applyInst);
543+
SILValue arraySIL = destructureInst->getResults()[0];
544+
SILValue storagePointerSIL = destructureInst->getResults()[1];
554545

555546
if (elements.empty()) {
556547
// Nothing more to be done if we are creating an empty array.
@@ -846,20 +837,21 @@ getEndPointsOfDataDependentChain(SILValue value, SILFunction *fun,
846837
}
847838
}
848839

849-
/// Given an instruction \p inst, invoke the given clean-up function \p cleanup
850-
/// on its lifetime frontier, which are instructions that follow the last use of
851-
/// the results of \c inst. E.g. the clean-up function could destory/release
852-
/// the function result.
853-
static void
854-
cleanupAtEndOfLifetime(SILInstruction *inst,
855-
llvm::function_ref<void(SILInstruction *)> cleanup) {
856-
ValueLifetimeAnalysis lifetimeAnalysis = ValueLifetimeAnalysis(inst);
857-
ValueLifetimeAnalysis::Frontier frontier;
858-
(void)lifetimeAnalysis.computeFrontier(
859-
frontier, ValueLifetimeAnalysis::AllowToModifyCFG);
860-
for (SILInstruction *lifetimeEndInst : frontier) {
861-
cleanup(lifetimeEndInst);
862-
}
840+
/// Given a guaranteed SILValue \p value, return a borrow-scope introducing
841+
/// value, if there is exactly one such introducing value. Otherwise, return
842+
/// None. There can be multiple borrow scopes for a SILValue iff it is derived
843+
/// from a guaranteed basic block parameter representing a phi node.
844+
static Optional<BorrowScopeIntroducingValue>
845+
getUniqueBorrowScopeIntroducingValue(SILValue value) {
846+
assert(value.getOwnershipKind() == ValueOwnershipKind::Guaranteed &&
847+
"parameter must be a guarenteed value");
848+
SmallVector<BorrowScopeIntroducingValue, 4> borrowIntroducers;
849+
getUnderlyingBorrowIntroducingValues(value, borrowIntroducers);
850+
assert(borrowIntroducers.size() > 0 &&
851+
"folding guaranteed value with no borrow introducer");
852+
if (borrowIntroducers.size() > 1)
853+
return None;
854+
return borrowIntroducers[0];
863855
}
864856

865857
/// Replace all uses of \c originalVal by \c foldedVal and adjust lifetimes of
@@ -888,45 +880,7 @@ static void replaceAllUsesAndFixLifetimes(SILValue foldedVal,
888880
return;
889881
}
890882
assert(!foldedVal->getType().isTrivial(*fun));
891-
892-
if (!fun->hasOwnership()) {
893-
// In non-ownership SIL, handle only folding of struct_extract instruction,
894-
// which is the only important instruction that should be folded by this
895-
// pass. The logic used here is not correct in general and overfits a
896-
// specific form of SIL. This code should be removed once OSSA is enabled
897-
// for this pass.
898-
// TODO: this code can be safely removed once ownership SIL becomes the
899-
// default SIL this pass works on.
900-
assert(isa<StructExtractInst>(originalInst) &&
901-
!originalVal->getType().isAddress());
902-
903-
// First, replace all uses of originalVal by foldedVal, and then adjust
904-
// their lifetimes if necessary.
905-
originalVal->replaceAllUsesWith(foldedVal);
906-
907-
unsigned retainCount = 0;
908-
unsigned consumeCount = 0;
909-
for (Operand *use : foldedVal->getUses()) {
910-
SILInstruction *user = use->getUser();
911-
if (isa<ReleaseValueInst>(user) || isa<StoreInst>(user))
912-
consumeCount++;
913-
if (isa<RetainValueInst>(user))
914-
retainCount++;
915-
// Note that there could other consuming operations but they are not
916-
// handled here as this code should be phased out soon.
917-
}
918-
if (consumeCount > retainCount) {
919-
// The original value was at +1 and therefore consumed at the end. Since
920-
// the foldedVal is also at +1 there is nothing to be done.
921-
return;
922-
}
923-
cleanupAtEndOfLifetime(foldedInst, [&](SILInstruction *lifetimeEndInst) {
924-
SILBuilderWithScope builder(lifetimeEndInst);
925-
builder.emitReleaseValue(lifetimeEndInst->getLoc(), foldedVal);
926-
});
927-
return;
928-
}
929-
883+
assert(fun->hasOwnership());
930884
assert(foldedVal.getOwnershipKind() == ValueOwnershipKind::Owned &&
931885
"constant value must have owned ownership kind");
932886

@@ -942,22 +896,36 @@ static void replaceAllUsesAndFixLifetimes(SILValue foldedVal,
942896
return;
943897
}
944898

945-
// Here, originalVal is not owned. Hence, borrow form foldedVal and use the
946-
// borrow in place of originalVal. Also, destroy foldedVal at the end of its
947-
// lifetime.
899+
// Here, originalVal is guaranteed. It must belong to a borrow scope that
900+
// begins at a scope introducing instruction e.g. begin_borrow or load_borrow.
901+
// The foldedVal should also have been inserted at the beginning of the scope.
902+
// Therefore, create a borrow of foldedVal at the beginning of the scope and
903+
// use the borrow in place of the originalVal. Also, end the borrow and
904+
// destroy foldedVal at the end of the borrow scope.
948905
assert(originalVal.getOwnershipKind() == ValueOwnershipKind::Guaranteed);
949906

950-
SILBuilderWithScope builder(&*std::next(foldedInst->getIterator()));
951-
BeginBorrowInst *borrow =
952-
builder.createBeginBorrow(foldedInst->getLoc(), foldedVal);
907+
Optional<BorrowScopeIntroducingValue> originalScopeBegin =
908+
getUniqueBorrowScopeIntroducingValue(originalVal);
909+
assert(originalScopeBegin &&
910+
"value without a unique borrow scope should not have been folded");
911+
SILInstruction *scopeBeginInst =
912+
originalScopeBegin->value->getDefiningInstruction();
913+
assert(scopeBeginInst);
914+
915+
SILBuilderWithScope builder(scopeBeginInst);
916+
SILValue borrow =
917+
builder.emitBeginBorrowOperation(scopeBeginInst->getLoc(), foldedVal);
953918

954919
originalVal->replaceAllUsesWith(borrow);
955920

956-
cleanupAtEndOfLifetime(borrow, [&](SILInstruction *lifetimeEndInst) {
957-
SILBuilderWithScope builder(lifetimeEndInst);
958-
builder.createEndBorrow(lifetimeEndInst->getLoc(), borrow);
959-
builder.emitDestroyValueOperation(lifetimeEndInst->getLoc(), foldedVal);
960-
});
921+
SmallVector<SILInstruction *, 4> scopeEndingInsts;
922+
originalScopeBegin->getLocalScopeEndingInstructions(scopeEndingInsts);
923+
924+
for (SILInstruction *scopeEndingInst : scopeEndingInsts) {
925+
SILBuilderWithScope builder(scopeEndingInst);
926+
builder.emitEndBorrowOperation(scopeEndingInst->getLoc(), borrow);
927+
builder.emitDestroyValueOperation(scopeEndingInst->getLoc(), foldedVal);
928+
}
961929
return;
962930
}
963931

@@ -977,13 +945,31 @@ static void substituteConstants(FoldState &foldState) {
977945
assert(definingInst);
978946
SILFunction *fun = definingInst->getFunction();
979947

980-
// Do not attempt to fold anything but struct_extract in non-OSSA.
981-
// TODO: this condition should be removed once migration OSSA is complete.
982-
if (!fun->hasOwnership() && !isa<StructExtractInst>(definingInst))
983-
continue;
948+
// Find an insertion point for inserting the new constant value. If we are
949+
// folding a value like struct_extract within a borrow scope, we need to
950+
// insert the constant value at the beginning of the borrow scope. This
951+
// is because the borrowed value is expected to be alive during its entire
952+
// borrow scope and could be stored into memory and accessed indirectly
953+
// without a copy e.g. using store_borrow within the borrow scope. On the
954+
// other hand, if we are folding an owned value, we can insert the constant
955+
// value at the point where the owned value is defined.
956+
SILInstruction *insertionPoint = definingInst;
957+
if (constantSILValue.getOwnershipKind() == ValueOwnershipKind::Guaranteed) {
958+
Optional<BorrowScopeIntroducingValue> borrowIntroducer =
959+
getUniqueBorrowScopeIntroducingValue(constantSILValue);
960+
if (!borrowIntroducer) {
961+
// This case happens only if constantSILValue is derived from a
962+
// guaranteed basic block parameter. This is unlikley because the values
963+
// that have to be folded should just be a struct-extract of an owned
964+
// instance of OSLogMessage.
965+
continue;
966+
}
967+
insertionPoint = borrowIntroducer->value->getDefiningInstruction();
968+
assert(insertionPoint && "borrow scope begnning is a parameter");
969+
}
984970

985-
SILBuilderWithScope builder(definingInst);
986-
SILLocation loc = definingInst->getLoc();
971+
SILBuilderWithScope builder(insertionPoint);
972+
SILLocation loc = insertionPoint->getLoc();
987973
CanType instType = constantSILValue->getType().getASTType();
988974
SILValue foldedSILVal = emitCodeForSymbolicValue(
989975
constantSymbolicVal, instType, builder, loc, foldState.stringInfo);
@@ -1067,6 +1053,7 @@ static bool constantFold(SILInstruction *start,
10671053
SingleValueInstruction *oslogMessage,
10681054
unsigned assertConfig) {
10691055
SILFunction *fun = start->getFunction();
1056+
assert(fun->hasOwnership() && "function not in ownership SIL");
10701057

10711058
// Initialize fold state.
10721059
SmallVector<SILInstruction *, 2> endUsersOfOSLogMessage;

0 commit comments

Comments
 (0)