bool StackPromoter::canPromoteAlloc(SILInstruction *AI, SILInstruction *&AllocInsertionPoint, SILInstruction *&DeallocInsertionPoint) { AllocInsertionPoint = nullptr; DeallocInsertionPoint = nullptr; auto *Node = ConGraph->getNodeOrNull(AI, EA); if (!Node) return false; // The most important check: does the object escape the current function? if (Node->escapes()) return false; // Now we have to determine the lifetime of the allocated object in its // function. // Get all interesting uses of the object (e.g. release instructions). This // includes uses of objects where the allocation is stored to. int NumUsePointsToFind = ConGraph->getNumUsePoints(Node); if (NumUsePointsToFind == 0) { // There should always be at least one release for an allocated object. // But in case all paths from this block end in unreachable then the // final release of the object may be optimized away. We bail out in this // case. return false; } // Try to find the point where to insert the deallocation. // This might need more than one try in case we need to move the allocation // out of a stack-alloc-dealloc pair. See findDeallocPoint(). SILInstruction *StartInst = AI; for (;;) { SILInstruction *RestartPoint = nullptr; DeallocInsertionPoint = findDeallocPoint(StartInst, RestartPoint, Node, NumUsePointsToFind); if (DeallocInsertionPoint) return true; if (!RestartPoint) return false; // Moving a buffer allocation call is not trivial because we would need to // move all the parameter calculations as well. So we just don't do it. if (!isa<AllocRefInst>(AI)) return false; // Retry with moving the allocation up. AllocInsertionPoint = RestartPoint; StartInst = RestartPoint; } }
bool StackPromoter::canPromoteAlloc(SILInstruction *AI, SILInstruction *&AllocInsertionPoint, SILInstruction *&DeallocInsertionPoint) { AllocInsertionPoint = nullptr; DeallocInsertionPoint = nullptr; auto *Node = ConGraph->getNodeOrNull(AI, EA); if (!Node) return false; // The most important check: does the object escape the current function? if (Node->escapes()) return false; // Now we have to determine the lifetime of the allocated object in its // function. // Get all interesting uses of the object (e.g. release instructions). This // includes uses of objects where the allocation is stored to. int NumUsePointsToFind = ConGraph->getNumUsePoints(Node); if (NumUsePointsToFind == 0) { // There should always be at least one release for an allocated object. // But in case all paths from this block end in unreachable then the // final release of the object may be optimized away. We bail out in this // case. return false; } // In the following we check two requirements for stack promotion: // 1) Are all uses in the same control region as the alloc? E.g. if the // allocation is in a loop then there may not be any uses of the object // outside the loop. // 2) We need to find an insertion place for the deallocation so that it // preserves a properly nested stack allocation-deallocation structure. SILBasicBlock *StartBlock = AI->getParent(); // The block where we assume we can insert the deallocation. SILBasicBlock *EndBlock = StartBlock; // We visit all instructions starting at the allocation instruction. WorkListType WorkList; // It's important that the EndBlock is at the head of the WorkList so that // we handle it after all other blocks. WorkList.insert(EndBlock, -1); WorkList.insert(StartBlock, 0); for (;;) { SILBasicBlock *BB = WorkList.pop_back_val(); int StackDepth = 0; SILBasicBlock::iterator Iter; if (BB == StartBlock) { // In the first block we start at the allocation instruction and not at // the begin of the block. Iter = AI->getIterator(); } else { // Track all uses in the block arguments. for (SILArgument *BBArg : BB->getBBArgs()) { if (ConGraph->isUsePoint(BBArg, Node)) NumUsePointsToFind--; } // Make sure that the EndBlock is not inside a loop (which does not // contain the StartBlock). // E.g.: // %obj = alloc_ref // the allocation // br loop // loop: // the_only_use_of_obj(%obj) // cond_br ..., loop, exit // exit: // ... // this is the new EndBlock for (SILBasicBlock *Pred : BB->getPreds()) { // Extend the lifetime region until the EndBlock post dominates the // StartBlock. while (!strictlyPostDominates(EndBlock, Pred)) { EndBlock = getImmediatePostDom(EndBlock); if (!EndBlock) return false; } } Iter = BB->begin(); StackDepth = WorkList.getStackDepth(BB); } // Visit all instructions of the current block. while (Iter != BB->end()) { SILInstruction &I = *Iter++; if (BB == EndBlock && StackDepth == 0 && NumUsePointsToFind == 0) { // We found a place to insert the stack deallocation. DeallocInsertionPoint = &I; return true; } if (I.isAllocatingStack()) { StackDepth++; } else if (I.isDeallocatingStack()) { if (StackDepth == 0) { // The allocation is inside a stack alloc-dealloc region and we are // now leaving this region without having found a place for the // deallocation. E.g. // E.g.: // %1 = alloc_stack // %obj = alloc_ref // the allocation // dealloc_stack %1 // use_of_obj(%obj) // // In this case we can move the alloc_ref before the alloc_stack // to fix the nesting. if (!isa<AllocRefInst>(AI)) return false; auto *Alloc = dyn_cast<SILInstruction>(I.getOperand(0)); if (!Alloc) return false; // This should always be the case, but let's be on the safe side. if (!PDT->dominates(StartBlock, Alloc->getParent())) return false; AllocInsertionPoint = Alloc; StackDepth++; } StackDepth--; } // Track a use. if (ConGraph->isUsePoint(&I, Node) != 0) NumUsePointsToFind--; } if (WorkList.empty()) { if (EndBlock == BB) { // We reached the EndBlock but didn't find a place for the deallocation // so far (because we didn't find all uses yet or we entered another // stack alloc-dealloc region). Let's extend our lifetime region. // E.g.: // %obj = alloc_ref // the allocation // %1 = alloc_stack // use_of_obj(%obj) // can't insert the deallocation in this block // cond_br ..., bb1, bb2 // bb1: // ... // br bb2 // bb2: // dealloc_stack %1 // this is the new EndBlock EndBlock = getImmediatePostDom(EndBlock); if (!EndBlock) return false; } // Again, it's important that the EndBlock is the first in the WorkList. WorkList.insert(EndBlock, -1); } // Push the successor blocks to the WorkList. for (SILBasicBlock *Succ : BB->getSuccessors()) { if (!strictlyDominates(StartBlock, Succ)) { // The StartBlock is inside a loop but we couldn't find a deallocation // place in this loop, e.g. because there are uses outside the loop. // E.g.: // %container = alloc_ref // br loop // loop: // %obj = alloc_ref // the allocation // store %obj to %some_field_in_container // cond_br ..., loop, exit // exit: // use(%container) return false; } WorkList.insert(Succ, StackDepth); } } }
SILInstruction *StackPromoter::findDeallocPoint(SILInstruction *StartInst, SILInstruction *&RestartPoint, EscapeAnalysis::CGNode *Node, int NumUsePointsToFind) { // In the following we check two requirements for stack promotion: // 1) Are all uses in the same control region as the alloc? E.g. if the // allocation is in a loop then there may not be any uses of the object // outside the loop. // 2) We need to find an insertion place for the deallocation so that it // preserves a properly nested stack allocation-deallocation structure. SILBasicBlock *StartBlock = StartInst->getParent(); // The block where we assume we can insert the deallocation. SILBasicBlock *EndBlock = StartBlock; // We visit all instructions starting at the allocation instruction. WorkListType WorkList; // It's important that the EndBlock is at the head of the WorkList so that // we handle it after all other blocks. WorkList.insert(EndBlock, -1); WorkList.insert(StartBlock, 0); for (;;) { SILBasicBlock *BB = WorkList.pop_back_val(); int StackDepth = 0; SILBasicBlock::iterator Iter; if (BB == StartBlock) { // In the first block we start at the allocation instruction and not at // the begin of the block. Iter = StartInst->getIterator(); } else { // Track all uses in the block arguments. for (SILArgument *BBArg : BB->getArguments()) { if (ConGraph->isUsePoint(BBArg, Node)) NumUsePointsToFind--; } // Make sure that the EndBlock is not inside a loop (which does not // contain the StartBlock). // E.g.: // %obj = alloc_ref // the allocation // br loop // loop: // the_only_use_of_obj(%obj) // cond_br ..., loop, exit // exit: // ... // this is the new EndBlock EndBlock = updateEndBlock(BB, EndBlock, WorkList); if (!EndBlock) return nullptr; Iter = BB->begin(); StackDepth = WorkList.getStackDepth(BB); } // Visit all instructions of the current block. while (Iter != BB->end()) { SILInstruction &I = *Iter++; if (BB == EndBlock && StackDepth == 0 && NumUsePointsToFind == 0) { // We found a place to insert the stack deallocation. return &I; } if (I.isAllocatingStack()) { StackDepth++; } else if (I.isDeallocatingStack()) { if (StackDepth == 0) { // The allocation is inside a stack alloc-dealloc region and we are // now leaving this region without having found a place for the // deallocation. E.g. // E.g.: // %1 = alloc_stack // %obj = alloc_ref // the allocation // dealloc_stack %1 // use_of_obj(%obj) // // In this case we can move the alloc_ref before the alloc_stack // to fix the nesting. auto *Alloc = dyn_cast<SILInstruction>(I.getOperand(0)); if (!Alloc) return nullptr; // This should always be the case, but let's be on the safe side. if (!postDominates(StartBlock, Alloc->getParent())) return nullptr; // Trigger another iteration with a new start point; RestartPoint = Alloc; return nullptr; } StackDepth--; } // Track a use. if (ConGraph->isUsePoint(&I, Node) != 0) NumUsePointsToFind--; } if (WorkList.empty()) { if (EndBlock == BB) { // We reached the EndBlock but didn't find a place for the deallocation // so far (because we didn't find all uses yet or we entered another // stack alloc-dealloc region). Let's extend our lifetime region. // E.g.: // %obj = alloc_ref // the allocation // %1 = alloc_stack // use_of_obj(%obj) // can't insert the deallocation in this block // cond_br ..., bb1, bb2 // bb1: // ... // br bb2 // bb2: // dealloc_stack %1 // this is the new EndBlock EndBlock = getImmediatePostDom(EndBlock); if (!EndBlock) return nullptr; } // Again, it's important that the EndBlock is the first in the WorkList. WorkList.insert(EndBlock, -1); } // Push the successor blocks to the WorkList. for (SILBasicBlock *Succ : BB->getSuccessors()) { if (!strictlyDominates(StartBlock, Succ)) { // The StartBlock is inside a loop but we couldn't find a deallocation // place in this loop, e.g. because there are uses outside the loop. // E.g.: // %container = alloc_ref // br loop // loop: // %obj = alloc_ref // the allocation // store %obj to %some_field_in_container // cond_br ..., loop, exit // exit: // use(%container) return nullptr; } WorkList.insert(Succ, StackDepth); } } }