Skip to content

Commit 708aadf

Browse files
committed
Cleanup SILCombine code in preparation for a bug fix.
This is not NFC because the isUseAfterFree helper and surrounding code is rewritten. The previous code's intention is unclear, but it was at best imprecise and unsafe w.r.t. future SIL changes. Pattern matching code that becomes highly complicated should be commented with motivating SIL examples.
1 parent fd11da5 commit 708aadf

File tree

1 file changed

+121
-88
lines changed

1 file changed

+121
-88
lines changed

lib/SILOptimizer/SILCombiner/SILCombinerApplyVisitors.cpp

Lines changed: 121 additions & 88 deletions
Original file line numberDiff line numberDiff line change
@@ -601,7 +601,7 @@ SILCombiner::optimizeConcatenationOfStringLiterals(ApplyInst *AI) {
601601
}
602602

603603
/// Returns the address of an object with which the stack location \p ASI is
604-
/// initialized. This is either a init_existential_addr or the source of a
604+
/// initialized. This is either a init_existential_addr or the destination of a
605605
/// copy_addr. Returns a null value if the address does not dominate the
606606
/// alloc_stack user \p ASIUser.
607607
/// If the value is copied from another stack location, \p isCopied is set to
@@ -808,21 +808,45 @@ SILCombiner::createApplyWithConcreteType(FullApplySite AI,
808808
return NewAI.getInstruction();
809809
}
810810

811+
namespace {
812+
/// Record conformance and concrete type info derived from init_existential.
813+
struct ConformanceAndConcreteType {
814+
Optional<ProtocolConformanceRef> Conformance;
815+
// Concrete type of self from the found init_existential.
816+
CanType ConcreteType;
817+
// For opened existentials, record the type definition.
818+
SingleValueInstruction *ConcreteTypeDef = nullptr;
819+
// The value of concrete type used to initialize the existential.
820+
SILValue NewSelf;
821+
// The value that owns the lifetime of NewSelf.
822+
// init_existential_addr's source address.
823+
// init_existential_ref's defined value.
824+
SILValue NewSelfOwner;
825+
ArrayRef<ProtocolConformanceRef> Conformances;
826+
827+
ConformanceAndConcreteType(ASTContext &Ctx, FullApplySite AI,
828+
SILInstruction *InitExistential,
829+
ProtocolDecl *Protocol);
830+
831+
bool isValid() const {
832+
return Conformance.hasValue() && ConcreteType && NewSelf;
833+
}
834+
835+
ProtocolConformanceRef getConformance() const {
836+
return Conformance.getValue();
837+
}
838+
};
839+
} // namespace
840+
811841
/// Derive a concrete type of self and conformance from the init_existential
812842
/// instruction.
813-
static Optional<std::tuple<ProtocolConformanceRef, CanType,
814-
SingleValueInstruction*, SILValue>>
815-
getConformanceAndConcreteType(ASTContext &Ctx,
816-
FullApplySite AI,
817-
SILInstruction *InitExistential,
818-
ProtocolDecl *Protocol,
819-
ArrayRef<ProtocolConformanceRef> &Conformances) {
820-
// Try to derive the concrete type of self from the found init_existential.
821-
CanType ConcreteType;
843+
/// If successful, initializes a valid ConformanceAndConcreteType.
844+
ConformanceAndConcreteType::ConformanceAndConcreteType(
845+
ASTContext &Ctx, FullApplySite AI, SILInstruction *InitExistential,
846+
ProtocolDecl *Protocol) {
847+
822848
// The existential type result of the found init_existential.
823849
CanType ExistentialType;
824-
SingleValueInstruction *ConcreteTypeDef = nullptr;
825-
SILValue NewSelf;
826850

827851
// FIXME: Factor this out. All we really need here is the ExistentialSig
828852
// below, which should be stored directly in the SILInstruction.
@@ -846,7 +870,8 @@ getConformanceAndConcreteType(ASTContext &Ctx,
846870
ConcreteType = cast<MetatypeType>(ConcreteType).getInstanceType();
847871
}
848872
} else {
849-
return None;
873+
assert(!isValid());
874+
return;
850875
}
851876

852877
// Construct a substitution map from the existential type's generic
@@ -860,9 +885,8 @@ getConformanceAndConcreteType(ASTContext &Ctx,
860885
// If the requirement is in a base protocol that is refined by the
861886
// conforming protocol, fish out the exact conformance for the base
862887
// protocol using the substitution map.
863-
auto ExactConformance = SubMap.lookupConformance(
864-
CanType(ExistentialSig->getGenericParams()[0]),
865-
Protocol);
888+
Conformance = SubMap.lookupConformance(
889+
CanType(ExistentialSig->getGenericParams()[0]), Protocol);
866890

867891
// If the concrete type is another existential, we're "forwarding" an
868892
// opened existential type, so we must keep track of the original
@@ -878,127 +902,136 @@ getConformanceAndConcreteType(ASTContext &Ctx,
878902
InitExistential->getTypeDependentOperands()[0].get());
879903
}
880904
}
881-
882-
return std::make_tuple(*ExactConformance, ConcreteType,
883-
ConcreteTypeDef, NewSelf);
905+
assert(isValid());
884906
}
885907

886-
static bool isUseAfterFree(SILValue val, SILInstruction *Apply,
887-
DominanceInfo *DT) {
888-
for (auto Use : val->getUses()) {
889-
auto *User = Use->getUser();
890-
if (!isa<DeallocStackInst>(User) && !isa<DestroyAddrInst>(User) &&
891-
!isa<DeinitExistentialAddrInst>(User)) {
892-
continue;
893-
}
894-
if (!DT->properlyDominates(Apply, User)) {
895-
// we have use-after-free - Conservative heuristic
896-
// Non conservative solution would require data flow analysis
908+
// Return true if the given value is guaranteed to be initialized across the
909+
// given call site.
910+
//
911+
// It's possible for an address to be initialized/deinitialized/reinitialized.
912+
// Rather than keeping track of liveness, we very conservatively check that all
913+
// deinitialization occures after the call.
914+
//
915+
// FIXME: Rather than whitelisting, use a common AllocStackAnalyzer.
916+
static bool isAddressInitializedAtCall(SILValue addr, SILInstruction *AI,
917+
DominanceInfo *DT) {
918+
auto isDestroy = [](Operand *use) {
919+
switch (use->getUser()->getKind()) {
920+
default:
921+
return false;
922+
case SILInstructionKind::DestroyAddrInst:
923+
case SILInstructionKind::DeinitExistentialAddrInst:
897924
return true;
925+
case SILInstructionKind::CopyAddrInst: {
926+
auto *copy = cast<CopyAddrInst>(use->getUser());
927+
return copy->getSrc() == use->get() && copy->isTakeOfSrc();
928+
}
929+
}
930+
};
931+
for (auto use : addr->getUses()) {
932+
SILInstruction *user = use->getUser();
933+
if (isDestroy(use)) {
934+
if (!DT->properlyDominates(AI, user))
935+
return false;
936+
} else {
937+
assert(isa<CopyAddrInst>(user) || isa<InitExistentialAddrInst>(user)
938+
|| isa<OpenExistentialAddrInst>(user)
939+
|| isa<DeallocStackInst>(user)
940+
|| isDebugInst(user) && "Unexpected instruction");
898941
}
899942
}
900-
return false;
943+
return true;
901944
}
902945

903946
/// Propagate information about a concrete type from init_existential_addr
904947
/// or init_existential_ref into witness_method conformances and into
905948
/// apply instructions.
906949
/// This helps the devirtualizer to replace witness_method by
907950
/// class_method instructions and then devirtualize.
908-
SILInstruction *
909-
SILCombiner::propagateConcreteTypeOfInitExistential(FullApplySite AI,
910-
ProtocolDecl *Protocol,
911-
llvm::function_ref<void(CanType , ProtocolConformanceRef)> Propagate) {
951+
SILInstruction *SILCombiner::propagateConcreteTypeOfInitExistential(
952+
FullApplySite Apply, ProtocolDecl *Protocol,
953+
llvm::function_ref<void(CanType, ProtocolConformanceRef)> Propagate) {
912954

913955
ASTContext &Ctx = Builder.getASTContext();
914956

915957
// Get the self argument.
916-
SILValue Self;
917-
if (auto *Apply = dyn_cast<ApplyInst>(AI)) {
918-
if (Apply->hasSelfArgument())
919-
Self = Apply->getSelfArgument();
920-
} else if (auto *Apply = dyn_cast<TryApplyInst>(AI)) {
921-
if (Apply->hasSelfArgument())
922-
Self = Apply->getSelfArgument();
923-
}
924-
925-
assert(Self && "Self argument should be present");
958+
assert(Apply.hasSelfArgument() && "Self argument should be present");
959+
SILValue Self = Apply.getSelfArgument();
926960

927961
// Try to find the init_existential, which could be used to
928962
// determine a concrete type of the self.
929963
ArchetypeType *OpenedArchetype = nullptr;
930964
SILValue OpenedArchetypeDef;
931965
bool isCopied = false;
932-
SILInstruction *InitExistential =
933-
findInitExistential(AI, Self, OpenedArchetype, OpenedArchetypeDef,
934-
isCopied);
966+
SILInstruction *InitExistential = findInitExistential(
967+
Apply, Self, OpenedArchetype, OpenedArchetypeDef, isCopied);
935968
if (!InitExistential)
936969
return nullptr;
937970

938971
// Try to derive the concrete type of self and a related conformance from
939972
// the found init_existential.
940-
ArrayRef<ProtocolConformanceRef> Conformances;
941-
auto ConformanceAndConcreteType =
942-
getConformanceAndConcreteType(Ctx, AI, InitExistential,
943-
Protocol, Conformances);
944-
if (!ConformanceAndConcreteType)
973+
ConformanceAndConcreteType CCT(Ctx, Apply, InitExistential, Protocol);
974+
if (!CCT.isValid())
945975
return nullptr;
946976

947-
ProtocolConformanceRef Conformance = std::get<0>(*ConformanceAndConcreteType);
948-
CanType ConcreteType = std::get<1>(*ConformanceAndConcreteType);
949-
auto ConcreteTypeDef = std::get<2>(*ConformanceAndConcreteType);
950-
SILValue NewSelf = std::get<3>(*ConformanceAndConcreteType);
951-
952977
SILOpenedArchetypesTracker *OldOpenedArchetypesTracker =
953978
Builder.getOpenedArchetypesTracker();
954979

955-
SILOpenedArchetypesTracker OpenedArchetypesTracker(AI.getFunction());
980+
SILOpenedArchetypesTracker OpenedArchetypesTracker(Apply.getFunction());
956981

957-
if (ConcreteType->isOpenedExistential()) {
958-
// Prepare a mini-mapping for opened archetypes.
959-
// SILOpenedArchetypesTracker OpenedArchetypesTracker(*AI.getFunction());
982+
if (CCT.ConcreteType->isOpenedExistential()) {
983+
// Temporarily record this opened existential def. Don't permanently record
984+
// in the Builder's tracker because this opened existential's original
985+
// dominating def may not have been recorded yet.
986+
// FIXME: Redesign the tracker. This is not robust.
960987
OpenedArchetypesTracker.addOpenedArchetypeDef(
961-
cast<ArchetypeType>(ConcreteType), ConcreteTypeDef);
988+
cast<ArchetypeType>(CCT.ConcreteType), CCT.ConcreteTypeDef);
962989
Builder.setOpenedArchetypesTracker(&OpenedArchetypesTracker);
963990
}
964991

965992
// Propagate the concrete type into the callee-operand if required.
966-
Propagate(ConcreteType, Conformance);
993+
Propagate(CCT.ConcreteType, CCT.getConformance());
967994

968-
if (isCopied) {
995+
auto canReplaceCopiedSelf = [&]() {
969996
// If the witness method is mutating self, we cannot replace self with
970997
// the source of a copy. Otherwise the call would modify another value than
971998
// the original self.
972-
switch (AI.getArgumentConvention(AI.getNumArguments() - 1)) {
973-
case SILArgumentConvention::ConventionType::Indirect_Inout:
974-
case SILArgumentConvention::ConventionType::Indirect_InoutAliasable:
975-
return nullptr;
976-
default:
977-
break;
978-
}
979-
// check if using the value in the apply would cause use-after-free
980-
auto *DT = DA->get(AI.getFunction());
981-
auto *apply = AI.getInstruction();
982-
auto op = InitExistential->getOperand(0);
983-
if (isUseAfterFree(op, apply, DT)) {
984-
return nullptr;
985-
}
986-
if (isUseAfterFree(NewSelf, apply, DT)) {
987-
return nullptr;
988-
}
989-
}
999+
if (Apply.getOrigCalleeType()->getSelfParameter().isIndirectMutating())
1000+
return false;
1001+
1002+
auto *DT = DA->get(Apply.getFunction());
1003+
auto *AI = Apply.getInstruction();
1004+
// Only init_existential_addr may be copied.
1005+
SILValue existentialAddr =
1006+
cast<InitExistentialAddrInst>(InitExistential)->getOperand();
1007+
return isAddressInitializedAtCall(existentialAddr, AI, DT);
1008+
};
1009+
1010+
// FIXME: Intentionally preserve this bug to avoid changing functionality.
1011+
if (isCopied && !canReplaceCopiedSelf())
1012+
return nullptr;
1013+
9901014
// Create a new apply instruction that uses the concrete type instead
9911015
// of the existential type.
992-
auto *NewAI = createApplyWithConcreteType(AI, NewSelf, Self, ConcreteType,
993-
ConcreteTypeDef, Conformance,
994-
OpenedArchetype);
1016+
auto *NewAI = createApplyWithConcreteType(
1017+
Apply, CCT.NewSelf, Self, CCT.ConcreteType, CCT.ConcreteTypeDef,
1018+
CCT.getConformance(), OpenedArchetype);
9951019

996-
if (ConcreteType->isOpenedExistential())
1020+
if (CCT.ConcreteType->isOpenedExistential())
9971021
Builder.setOpenedArchetypesTracker(OldOpenedArchetypesTracker);
9981022

9991023
return NewAI;
10001024
}
10011025

1026+
/// Rewrite a witness method's lookup type from an archetype to a concrete type.
1027+
/// Example:
1028+
/// %existential = alloc_stack $Protocol
1029+
/// %value = init_existential_addr %existential : $Concrete
1030+
/// copy_addr ... to %value
1031+
/// %witness = witness_method $@opened
1032+
/// apply %witness<T : Protocol>(%existential)
1033+
///
1034+
/// ==> apply %witness<Concrete : Protocol>(%existential)
10021035
SILInstruction *
10031036
SILCombiner::propagateConcreteTypeOfInitExistential(FullApplySite AI,
10041037
WitnessMethodInst *WMI) {
@@ -1097,7 +1130,7 @@ SILCombiner::propagateConcreteTypeOfInitExistential(FullApplySite AI) {
10971130
return nullptr;
10981131
}
10991132

1100-
// Obtain the protocol whose which should be used by the conformance.
1133+
// Obtain the protocol which should be used by the conformance.
11011134
auto *AFD = dyn_cast<AbstractFunctionDecl>(Callee->getDeclContext());
11021135
if (!AFD)
11031136
return nullptr;

0 commit comments

Comments
 (0)