/// Perform the apply{partial_apply(x,y)}(z) -> apply(z,x,y) peephole /// by iterating over all uses of the partial_apply and searching /// for the pattern to transform. SILInstruction *PartialApplyCombiner::combine() { if (!canCombinePartialApply(PAI)) return nullptr; // Only process partial apply if the callee is a known function. FRI = dyn_cast<FunctionRefInst>(PAI->getCallee()); // Iterate over all uses of the partial_apply // and look for applies that use it as a callee. for (auto UI = PAI->use_begin(), UE = PAI->use_end(); UI != UE; ) { auto Use = *UI; ++UI; auto User = Use->getUser(); // If this use of a partial_apply is not // an apply which uses it as a callee, bail. auto AI = FullApplySite::isa(User); if (!AI) continue; if (AI.getCallee() != PAI) continue; // We cannot handle generic apply yet. Bail. if (AI.hasSubstitutions()) continue; if (!processSingleApply(AI)) return nullptr; } // release/destroy and deallocate introduced temporaries. if (!Tmps.empty()) { releaseTemporaries(); deallocateTemporaries(); } return nullptr; }
/// Returns true on success. bool PartialApplyCombiner::allocateTemporaries() { // Copy the original arguments of the partial_apply into // newly created temporaries and use these temporaries instead of // the original arguments afterwards. // This is done to "extend" the life-time of original partial_apply // arguments, as they may be destroyed/deallocated before the last // use by one of the apply instructions. // TODO: // Copy arguments of the partial_apply into new temporaries // only if the lifetime of arguments ends before their uses // by apply instructions. bool needsReleases = false; CanSILFunctionType PAITy = dyn_cast<SILFunctionType>(PAI->getCallee()->getType().getSwiftType()); // Emit a destroy value for each captured closure argument. ArrayRef<SILParameterInfo> Params = PAITy->getParameters(); auto Args = PAI->getArguments(); unsigned Delta = Params.size() - Args.size(); llvm::SmallVector<std::pair<SILValue, unsigned>, 8> ArgsToHandle; for (unsigned AI = 0, AE = Args.size(); AI != AE; ++AI) { SILValue Arg = Args[AI]; SILParameterInfo Param = Params[AI + Delta]; if (Param.isIndirectMutating()) continue; // Create a temporary and copy the argument into it, if: // - the argument stems from an alloc_stack // - the argument is consumed by the callee and is indirect // (e.g. it is an @in argument) if (isa<AllocStackInst>(Arg) || (Param.isConsumed() && Param.isIndirect())) { // If the temporary is non-trivial, we need to release it later. if (!Arg->getType().isTrivial(PAI->getModule())) needsReleases = true; ArgsToHandle.push_back(std::make_pair(Arg, AI)); } } if (needsReleases) { // Compute the set of endpoints, which will be used to insert releases of // temporaries. This may fail if the frontier is located on a critical edge // which we may not split (no CFG changes in SILCombine). ValueLifetimeAnalysis VLA(PAI); if (!VLA.computeFrontier(PAFrontier, ValueLifetimeAnalysis::DontModifyCFG)) return false; } for (auto ArgWithIdx : ArgsToHandle) { SILValue Arg = ArgWithIdx.first; Builder.setInsertionPoint(PAI->getFunction()->begin()->begin()); // Create a new temporary at the beginning of a function. auto *Tmp = Builder.createAllocStack(PAI->getLoc(), Arg->getType(), {/*Constant*/ true, ArgWithIdx.second}); Builder.setInsertionPoint(PAI); // Copy argument into this temporary. Builder.createCopyAddr(PAI->getLoc(), Arg, Tmp, IsTake_t::IsNotTake, IsInitialization_t::IsInitialization); Tmps.push_back(Tmp); ArgToTmp.insert(std::make_pair(Arg, Tmp)); } return true; }
static void checkNoEscapePartialApplyUse(Operand *oper, FollowUse followUses) { SILInstruction *user = oper->getUser(); // Ignore uses that are totally uninteresting. if (isIncidentalUse(user) || onlyAffectsRefCount(user)) return; // Before checking conversions in general below (getSingleValueCopyOrCast), // check for convert_function to [without_actually_escaping]. Assume such // conversion are not actually escaping without following their uses. if (auto *CFI = dyn_cast<ConvertFunctionInst>(user)) { if (CFI->withoutActuallyEscaping()) return; } // Look through copies, borrows, and conversions. // // Note: This handles ConversionInst, which already includes everything in // swift::stripConvertFunctions. if (SingleValueInstruction *copy = getSingleValueCopyOrCast(user)) { // Only follow the copied operand. Other operands are incidental, // as in the second operand of mark_dependence. if (oper->getOperandNumber() == 0) followUses(copy); return; } switch (user->getKind()) { default: break; // Look through Optionals. case SILInstructionKind::EnumInst: // @noescape block storage can be passed as an Optional (Nullable). followUses(cast<EnumInst>(user)); return; // Look through Phis. case SILInstructionKind::BranchInst: { const SILPhiArgument *arg = cast<BranchInst>(user)->getArgForOperand(oper); followUses(arg); return; } case SILInstructionKind::CondBranchInst: { const SILPhiArgument *arg = cast<CondBranchInst>(user)->getArgForOperand(oper); if (arg) // If the use isn't the branch condition, follow it. followUses(arg); return; } // Look through ObjC closures. case SILInstructionKind::StoreInst: if (oper->getOperandNumber() == StoreInst::Src) { if (auto *PBSI = dyn_cast<ProjectBlockStorageInst>( cast<StoreInst>(user)->getDest())) { SILValue storageAddr = PBSI->getOperand(); // The closure is stored to block storage. Recursively visit all // uses of any initialized block storage values derived from this // storage address.. for (Operand *oper : storageAddr->getUses()) { if (auto *IBS = dyn_cast<InitBlockStorageHeaderInst>(oper->getUser())) followUses(IBS); } return; } } break; case SILInstructionKind::IsEscapingClosureInst: // May be generated by withoutActuallyEscaping. return; case SILInstructionKind::PartialApplyInst: { // Recurse through partial_apply to handle special cases before handling // ApplySites in general below. PartialApplyInst *PAI = cast<PartialApplyInst>(user); // Use the same logic as checkForViolationAtApply applied to a def-use // traversal. // // checkForViolationAtApply recurses through partial_apply chains. if (oper->get() == PAI->getCallee()) { followUses(PAI); return; } // checkForViolationAtApply also uses findClosuresForAppliedArg which in // turn checks isPartialApplyOfReabstractionThunk. // // A closure with @inout_aliasable arguments may be applied to a // thunk as "escaping", but as long as the thunk is only used as a // '@noescape" type then it is safe. if (isPartialApplyOfReabstractionThunk(PAI)) { // Don't follow thunks that were generated by withoutActuallyEscaping. SILFunction *thunkDef = PAI->getReferencedFunction(); if (!thunkDef->isWithoutActuallyEscapingThunk()) followUses(PAI); return; } // Handle this use like a normal applied argument. break; } }; // Handle ApplySites in general after checking PartialApply above. if (isa<ApplySite>(user)) { SILValue arg = oper->get(); auto argumentFnType = getSILFunctionTypeForValue(arg); if (argumentFnType && argumentFnType->isNoEscape()) { // Verify that the inverse operation, finding a partial_apply from a // @noescape argument, is consistent. TinyPtrVector<PartialApplyInst *> partialApplies; findClosuresForFunctionValue(arg, partialApplies); assert(!partialApplies.empty() && "cannot find partial_apply from @noescape function argument"); return; } llvm::dbgs() << "Applied argument must be @noescape function type: " << *arg; } else llvm::dbgs() << "Unexpected partial_apply use: " << *user; llvm_unreachable("A partial_apply with @inout_aliasable may only be " "used as a @noescape function type argument."); }