blob: db30ddf7d5328cc9fb96f0e61f4480b02f9aadd7 [file] [log] [blame]
//===--- SideEffectAnalysis.cpp - SIL Side Effect Analysis ----------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2018 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//
#define DEBUG_TYPE "sil-sea"
#include "swift/SILOptimizer/Analysis/SideEffectAnalysis.h"
#include "swift/SIL/SILArgument.h"
#include "swift/SILOptimizer/Analysis/AccessedStorageAnalysis.h"
#include "swift/SILOptimizer/Analysis/BasicCalleeAnalysis.h"
#include "swift/SILOptimizer/Analysis/FunctionOrder.h"
#include "swift/SILOptimizer/PassManager/PassManager.h"
using namespace swift;
// -----------------------------------------------------------------------------
// GenericFunctionEffectAnalysis
// -----------------------------------------------------------------------------
template <typename FunctionEffects>
void GenericFunctionEffectAnalysis<FunctionEffects>::initialize(
SILPassManager *PM) {
BCA = PM->getAnalysis<BasicCalleeAnalysis>();
}
template <typename FunctionEffects>
void GenericFunctionEffectAnalysis<FunctionEffects>::invalidate() {
functionInfoMap.clear();
allocator.DestroyAll();
LLVM_DEBUG(llvm::dbgs() << "invalidate all\n");
}
template <typename FunctionEffects>
void GenericFunctionEffectAnalysis<FunctionEffects>::invalidate(
SILFunction *F, InvalidationKind K) {
if (FunctionInfo *FInfo = functionInfoMap.lookup(F)) {
LLVM_DEBUG(llvm::dbgs() << " invalidate " << FInfo->F->getName() << '\n');
invalidateIncludingAllCallers(FInfo);
}
}
template <typename FunctionEffects>
void GenericFunctionEffectAnalysis<FunctionEffects>::getCalleeEffects(
FunctionEffects &calleeEffects, FullApplySite fullApply) {
if (calleeEffects.summarizeCall(fullApply))
return;
auto callees = BCA->getCalleeList(fullApply);
if (!callees.allCalleesVisible() ||
// @callee_owned function calls implicitly release the context, which
// may call deinits of boxed values.
// TODO: be less conservative about what destructors might be called.
fullApply.getOrigCalleeType()->isCalleeConsumed()) {
calleeEffects.setWorstEffects();
return;
}
// We can see all the callees, so merge the effects from all of them.
for (auto *callee : callees)
calleeEffects.mergeFrom(getEffects(callee));
}
template <typename FunctionEffects>
void GenericFunctionEffectAnalysis<FunctionEffects>::analyzeFunction(
FunctionInfo *functionInfo, FunctionOrder &bottomUpOrder,
int recursionDepth) {
functionInfo->needUpdateCallers = true;
if (bottomUpOrder.prepareForVisiting(functionInfo))
return;
auto *F = functionInfo->F;
if (functionInfo->functionEffects.summarizeFunction(F))
return;
LLVM_DEBUG(llvm::dbgs() << " >> analyze " << F->getName() << '\n');
// Check all instructions of the function
for (auto &BB : *F) {
for (auto &I : BB) {
if (auto fullApply = FullApplySite::isa(&I))
analyzeCall(functionInfo, fullApply, bottomUpOrder, recursionDepth);
else
functionInfo->functionEffects.analyzeInstruction(&I);
}
}
LLVM_DEBUG(llvm::dbgs() << " << finished " << F->getName() << '\n');
}
template <typename FunctionEffects>
void GenericFunctionEffectAnalysis<FunctionEffects>::analyzeCall(
FunctionInfo *functionInfo, FullApplySite fullApply,
FunctionOrder &bottomUpOrder, int recursionDepth) {
FunctionEffects applyEffects;
if (applyEffects.summarizeCall(fullApply)) {
functionInfo->functionEffects.mergeFromApply(applyEffects, fullApply);
return;
}
if (recursionDepth >= MaxRecursionDepth) {
functionInfo->functionEffects.setWorstEffects();
return;
}
CalleeList callees = BCA->getCalleeList(fullApply);
if (!callees.allCalleesVisible() ||
// @callee_owned function calls implicitly release the context, which
// may call deinits of boxed values.
// TODO: be less conservative about what destructors might be called.
fullApply.getOrigCalleeType()->isCalleeConsumed()) {
functionInfo->functionEffects.setWorstEffects();
return;
}
// Derive the effects of the apply from the known callees.
// Defer merging callee effects until the callee is scheduled
for (SILFunction *callee : callees) {
FunctionInfo *calleeInfo = getFunctionInfo(callee);
calleeInfo->addCaller(functionInfo, fullApply);
if (!calleeInfo->isVisited()) {
// Recursively visit the called function.
analyzeFunction(calleeInfo, bottomUpOrder, recursionDepth + 1);
bottomUpOrder.tryToSchedule(calleeInfo);
}
}
}
template <typename FunctionEffects>
void GenericFunctionEffectAnalysis<FunctionEffects>::recompute(
FunctionInfo *initialInfo) {
allocNewUpdateID();
LLVM_DEBUG(llvm::dbgs() << "recompute function-effect analysis with UpdateID "
<< getCurrentUpdateID() << '\n');
// Collect and analyze all functions to recompute, starting at initialInfo.
FunctionOrder bottomUpOrder(getCurrentUpdateID());
analyzeFunction(initialInfo, bottomUpOrder, 0);
// Build the bottom-up order.
bottomUpOrder.tryToSchedule(initialInfo);
bottomUpOrder.finishScheduling();
// Second step: propagate the side-effect information up the call-graph until
// it stabilizes.
bool needAnotherIteration;
do {
LLVM_DEBUG(llvm::dbgs() << "new iteration\n");
needAnotherIteration = false;
for (FunctionInfo *functionInfo : bottomUpOrder) {
if (!functionInfo->needUpdateCallers)
continue;
LLVM_DEBUG(llvm::dbgs() << " update callers of "
<< functionInfo->F->getName() << '\n');
functionInfo->needUpdateCallers = false;
// Propagate the function effects to all callers.
for (const auto &E : functionInfo->getCallers()) {
assert(E.isValid());
// Only include callers which we are actually recomputing.
if (!bottomUpOrder.wasRecomputedWithCurrentUpdateID(E.Caller))
continue;
LLVM_DEBUG(llvm::dbgs() << " merge into caller "
<< E.Caller->F->getName() << '\n');
if (E.Caller->functionEffects.mergeFromApply(
functionInfo->functionEffects, FullApplySite(E.FAS))) {
E.Caller->needUpdateCallers = true;
if (!E.Caller->isScheduledAfter(functionInfo)) {
// This happens if we have a cycle in the call-graph.
needAnotherIteration = true;
}
}
}
}
} while (needAnotherIteration);
}
// Instantiate template members.
template class swift::GenericFunctionEffectAnalysis<FunctionSideEffects>;
template class swift::GenericFunctionEffectAnalysis<FunctionAccessedStorage>;
// -----------------------------------------------------------------------------
// FunctionSideEffects
// -----------------------------------------------------------------------------
using MemoryBehavior = SILInstruction::MemoryBehavior;
MemoryBehavior
FunctionSideEffects::getMemBehavior(RetainObserveKind ScanKind) const {
bool Observe = (ScanKind == RetainObserveKind::ObserveRetains);
if ((Observe && mayAllocObjects()) || mayReadRC())
return MemoryBehavior::MayHaveSideEffects;
// Start with the global effects.
auto Behavior = GlobalEffects.getMemBehavior(ScanKind);
// Add effects from the parameters.
for (auto &ParamEffect : ParamEffects) {
MemoryBehavior ArgBehavior = ParamEffect.getMemBehavior(ScanKind);
Behavior = combineMemoryBehavior(Behavior, ArgBehavior);
// Stop the scan if we've reached the highest level of side effect.
if (Behavior == MemoryBehavior::MayHaveSideEffects)
break;
}
return Behavior;
}
bool FunctionSideEffects::mergeFrom(const FunctionSideEffects &RHS) {
bool Changed = mergeFlags(RHS);
Changed |= GlobalEffects.mergeFrom(RHS.GlobalEffects);
Changed |= LocalEffects.mergeFrom(RHS.LocalEffects);
// In case of an external function, the RHS may have 0 arguments.
unsigned NumArgs = RHS.ParamEffects.size();
for (unsigned Idx = 0; Idx < NumArgs; Idx++) {
// In case of a partial_apply, the RHS (= callee) may have more arguments
// than the apply instruction.
if (Idx < ParamEffects.size()) {
Changed |= ParamEffects[Idx].mergeFrom(RHS.ParamEffects[Idx]);
} else {
Changed |= GlobalEffects.mergeFrom(RHS.ParamEffects[Idx]);
}
}
return Changed;
}
bool FunctionSideEffects::mergeFromApply(
const FunctionSideEffects &ApplyEffects, FullApplySite FAS) {
bool Changed = mergeFlags(ApplyEffects);
Changed |= GlobalEffects.mergeFrom(ApplyEffects.GlobalEffects);
unsigned numCallerArgs = FAS.getNumArguments();
unsigned numCalleeArgs = ApplyEffects.ParamEffects.size();
assert(numCalleeArgs >= numCallerArgs);
for (unsigned Idx = 0; Idx < numCalleeArgs; Idx++) {
// Map the callee argument effects to parameters of this function.
// If there are more callee parameters than arguments it means that the
// callee is the result of a partial_apply.
FunctionSideEffectFlags *E = (Idx < numCallerArgs
? getEffectsOn(FAS.getArgument(Idx))
: &GlobalEffects);
Changed |= E->mergeFrom(ApplyEffects.ParamEffects[Idx]);
}
return Changed;
}
void FunctionSideEffects::dump() const { llvm::errs() << *this << '\n'; }
static SILValue skipAddrProjections(SILValue V) {
for (;;) {
switch (V->getKind()) {
case ValueKind::IndexAddrInst:
case ValueKind::IndexRawPointerInst:
case ValueKind::StructElementAddrInst:
case ValueKind::TupleElementAddrInst:
case ValueKind::RefElementAddrInst:
case ValueKind::RefTailAddrInst:
case ValueKind::ProjectBoxInst:
case ValueKind::UncheckedTakeEnumDataAddrInst:
case ValueKind::PointerToAddressInst:
V = cast<SingleValueInstruction>(V)->getOperand(0);
break;
default:
return V;
}
}
llvm_unreachable("there is no escape from an infinite loop");
}
static SILValue skipValueProjections(SILValue V) {
for (;;) {
switch (V->getKind()) {
case ValueKind::StructExtractInst:
case ValueKind::TupleExtractInst:
case ValueKind::UncheckedEnumDataInst:
case ValueKind::UncheckedTrivialBitCastInst:
case ValueKind::UncheckedRefCastInst:
V = cast<SingleValueInstruction>(V)->getOperand(0);
break;
default:
return V;
}
}
llvm_unreachable("there is no escape from an infinite loop");
}
FunctionSideEffectFlags *FunctionSideEffects::getEffectsOn(SILValue Addr) {
SILValue BaseAddr = skipValueProjections(skipAddrProjections(Addr));
switch (BaseAddr->getKind()) {
case swift::ValueKind::SILFunctionArgument: {
// Can we associate the address to a function parameter?
auto *Arg = cast<SILFunctionArgument>(BaseAddr);
return &ParamEffects[Arg->getIndex()];
break;
}
case ValueKind::AllocStackInst:
case ValueKind::AllocRefInst:
case ValueKind::AllocRefDynamicInst:
case ValueKind::AllocBoxInst:
// Effects on locally allocated storage.
return &LocalEffects;
default:
break;
}
// Everything else.
return &GlobalEffects;
}
// Return true if the given function has defined effects that were successfully
// recorded in this FunctionSideEffects object.
bool FunctionSideEffects::setDefinedEffects(SILFunction *F) {
if (F->hasSemanticsAttr(SEMANTICS_PROGRAMTERMINATION_POINT)) {
Traps = true;
return true;
}
switch (F->getEffectsKind()) {
case EffectsKind::ReleaseNone:
GlobalEffects.Reads = true;
GlobalEffects.Writes = true;
GlobalEffects.Releases = false;
return true;
case EffectsKind::ReadNone:
return true;
case EffectsKind::ReadOnly:
// @_effects(readonly) is worthless if we have owned parameters, because
// the release inside the callee may call a deinit, which itself can do
// anything.
if (!F->hasOwnedParameters()) {
GlobalEffects.Reads = true;
return true;
}
break;
default:
break;
}
return false;
}
// Return true if this function's effects have been fully summarized in this
// FunctionSideEffects object without visiting its body.
bool FunctionSideEffects::summarizeFunction(SILFunction *F) {
assert(ParamEffects.empty() && "Expect uninitialized effects.");
if (F->isDynamicallyReplaceable()) {
LLVM_DEBUG(llvm::dbgs()
<< " -- is dynamically_replaceable " << F->getName() << '\n');
setWorstEffects();
return true;
}
if (!F->empty())
ParamEffects.resize(F->getArguments().size());
// Handle @_effects attributes
if (setDefinedEffects(F)) {
LLVM_DEBUG(llvm::dbgs() << " -- has defined effects " << F->getName()
<< '\n');
return true;
}
if (!F->isDefinition()) {
// We can't assume anything about external functions.
LLVM_DEBUG(llvm::dbgs() << " -- is external " << F->getName() << '\n');
setWorstEffects();
return true;
}
return false;
}
// Return true if the side effects of this semantic call are fully known without
// visiting the callee and have been recorded in this FunctionSideEffects
// object.
bool FunctionSideEffects::setSemanticEffects(ArraySemanticsCall ASC) {
assert(ASC.hasSelf());
auto &SelfEffects = ParamEffects[ParamEffects.size() - 1];
// Currently we only handle array semantics.
// TODO: also handle other semantic functions.
switch (ASC.getKind()) {
case ArrayCallKind::kGetCount:
case ArrayCallKind::kGetCapacity:
if (!ASC.mayHaveBridgedObjectElementType()) {
SelfEffects.Reads = true;
SelfEffects.Releases |= !ASC.hasGuaranteedSelf();
return true;
}
return false;
case ArrayCallKind::kCheckSubscript:
case ArrayCallKind::kCheckIndex:
if (!ASC.mayHaveBridgedObjectElementType()) {
SelfEffects.Reads = true;
SelfEffects.Releases |= !ASC.hasGuaranteedSelf();
Traps = true;
return true;
}
return false;
case ArrayCallKind::kGetElement:
if (!ASC.mayHaveBridgedObjectElementType()) {
SelfEffects.Reads = true;
SelfEffects.Releases |= !ASC.hasGuaranteedSelf();
for (auto i : range(((ApplyInst *)ASC)
->getOrigCalleeConv()
.getNumIndirectSILResults())) {
assert(!ASC.hasGetElementDirectResult());
ParamEffects[i].Writes = true;
}
return true;
}
return false;
case ArrayCallKind::kArrayPropsIsNativeTypeChecked:
SelfEffects.Releases |= !ASC.hasGuaranteedSelf();
// The isNative checks evaluate to a constant (no read!) if the array
// cannot be bridged.
if (ASC.mayHaveBridgedObjectElementType())
SelfEffects.Reads = true;
return true;
case ArrayCallKind::kGetElementAddress:
SelfEffects.Reads = true;
SelfEffects.Releases |= !ASC.hasGuaranteedSelf();
return true;
case ArrayCallKind::kMakeMutable:
if (!ASC.mayHaveBridgedObjectElementType()) {
SelfEffects.Writes = true;
GlobalEffects.Releases = true;
AllocsObjects = true;
ReadsRC = true;
return true;
}
return false;
default:
return false;
}
}
// Summarize the callee side effects of a call instruction using this
// FunctionSideEffects object without analyzing the callee function bodies or
// scheduling the callees for bottom-up propagation.
//
// Return true if this call-site's effects are summarized without visiting the
// callee.
bool FunctionSideEffects::summarizeCall(FullApplySite fullApply) {
assert(ParamEffects.empty() && "Expect uninitialized effects.");
ParamEffects.resize(fullApply.getNumArguments());
// Is this a call to a semantics function?
if (auto apply = dyn_cast<ApplyInst>(fullApply.getInstruction())) {
ArraySemanticsCall ASC(apply);
if (ASC && ASC.hasSelf()) {
if (setSemanticEffects(ASC))
return true;
}
}
if (SILFunction *SingleCallee = fullApply.getReferencedFunction()) {
// Does the function have any @_effects?
if (setDefinedEffects(SingleCallee))
return true;
}
return false;
}
void FunctionSideEffects::analyzeInstruction(SILInstruction *I) {
// Handle some kind of instructions specially.
switch (I->getKind()) {
case SILInstructionKind::FixLifetimeInst:
// A fix_lifetime instruction acts like a read on the operand. Retains can
// move after it but the last release can't move before it.
getEffectsOn(I->getOperand(0))->Reads = true;
return;
case SILInstructionKind::AllocStackInst:
case SILInstructionKind::DeallocStackInst:
return;
#define ALWAYS_OR_SOMETIMES_LOADABLE_CHECKED_REF_STORAGE(Name, ...) \
case SILInstructionKind::Name##RetainInst: \
case SILInstructionKind::StrongRetain##Name##Inst: \
case SILInstructionKind::Copy##Name##ValueInst:
#include "swift/AST/ReferenceStorage.def"
case SILInstructionKind::StrongRetainInst:
case SILInstructionKind::RetainValueInst:
getEffectsOn(I->getOperand(0))->Retains = true;
return;
#define ALWAYS_OR_SOMETIMES_LOADABLE_CHECKED_REF_STORAGE(Name, ...) \
case SILInstructionKind::Name##ReleaseInst:
#include "swift/AST/ReferenceStorage.def"
case SILInstructionKind::StrongReleaseInst:
case SILInstructionKind::ReleaseValueInst:
getEffectsOn(I->getOperand(0))->Releases = true;
return;
case SILInstructionKind::UnconditionalCheckedCastInst:
getEffectsOn(cast<UnconditionalCheckedCastInst>(I)->getOperand())->Reads =
true;
Traps = true;
return;
case SILInstructionKind::LoadInst:
getEffectsOn(cast<LoadInst>(I)->getOperand())->Reads = true;
return;
case SILInstructionKind::StoreInst:
getEffectsOn(cast<StoreInst>(I)->getDest())->Writes = true;
return;
case SILInstructionKind::CondFailInst:
Traps = true;
return;
case SILInstructionKind::PartialApplyInst: {
AllocsObjects = true;
auto *PAI = cast<PartialApplyInst>(I);
auto Args = PAI->getArguments();
auto Params = PAI->getSubstCalleeType()->getParameters();
Params = Params.slice(Params.size() - Args.size(), Args.size());
for (unsigned Idx : indices(Args)) {
if (isIndirectFormalParameter(Params[Idx].getConvention()))
getEffectsOn(Args[Idx])->Reads = true;
}
return;
}
case SILInstructionKind::BuiltinInst: {
auto *BInst = cast<BuiltinInst>(I);
auto &BI = BInst->getBuiltinInfo();
switch (BI.ID) {
case BuiltinValueKind::IsUnique:
// TODO: derive this information in a more general way, e.g. add it
// to Builtins.def
ReadsRC = true;
break;
case BuiltinValueKind::CondUnreachable:
Traps = true;
return;
default:
break;
}
const IntrinsicInfo &IInfo = BInst->getIntrinsicInfo();
if (IInfo.ID == llvm::Intrinsic::trap) {
Traps = true;
return;
}
// Detailed memory effects of builtins are handled below by checking the
// memory behavior of the instruction.
break;
}
default:
break;
}
if (isa<AllocationInst>(I)) {
// Excluding AllocStackInst (which is handled above).
AllocsObjects = true;
}
// Check the general memory behavior for instructions we didn't handle above.
switch (I->getMemoryBehavior()) {
case MemoryBehavior::None:
break;
case MemoryBehavior::MayRead:
GlobalEffects.Reads = true;
break;
case MemoryBehavior::MayWrite:
GlobalEffects.Writes = true;
break;
case MemoryBehavior::MayReadWrite:
GlobalEffects.Reads = true;
GlobalEffects.Writes = true;
break;
case MemoryBehavior::MayHaveSideEffects:
setWorstEffects();
break;
}
if (I->mayTrap())
Traps = true;
}
SILAnalysis *swift::createSideEffectAnalysis(SILModule *M) {
return new SideEffectAnalysis();
}