1c095afcbSMogball //===- DeadCodeAnalysis.cpp - Dead code analysis --------------------------===// 2c095afcbSMogball // 3c095afcbSMogball // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. 4c095afcbSMogball // See https://llvm.org/LICENSE.txt for license information. 5c095afcbSMogball // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception 6c095afcbSMogball // 7c095afcbSMogball //===----------------------------------------------------------------------===// 8c095afcbSMogball 9c095afcbSMogball #include "mlir/Analysis/DataFlow/DeadCodeAnalysis.h" 10c095afcbSMogball #include "mlir/Analysis/DataFlow/ConstantPropagationAnalysis.h" 11c095afcbSMogball #include "mlir/Interfaces/CallInterfaces.h" 12c095afcbSMogball #include "mlir/Interfaces/ControlFlowInterfaces.h" 13c095afcbSMogball 14c095afcbSMogball using namespace mlir; 15c095afcbSMogball using namespace mlir::dataflow; 16c095afcbSMogball 17c095afcbSMogball //===----------------------------------------------------------------------===// 18c095afcbSMogball // Executable 19c095afcbSMogball //===----------------------------------------------------------------------===// 20c095afcbSMogball 21c095afcbSMogball ChangeResult Executable::setToLive() { 22c095afcbSMogball if (live) 23c095afcbSMogball return ChangeResult::NoChange; 24c095afcbSMogball live = true; 25c095afcbSMogball return ChangeResult::Change; 26c095afcbSMogball } 27c095afcbSMogball 28c095afcbSMogball void Executable::print(raw_ostream &os) const { 29c095afcbSMogball os << (live ? "live" : "dead"); 30c095afcbSMogball } 31c095afcbSMogball 32c095afcbSMogball void Executable::onUpdate(DataFlowSolver *solver) const { 33c095afcbSMogball if (auto *block = point.dyn_cast<Block *>()) { 34c095afcbSMogball // Re-invoke the analyses on the block itself. 35c095afcbSMogball for (DataFlowAnalysis *analysis : subscribers) 36c095afcbSMogball solver->enqueue({block, analysis}); 37c095afcbSMogball // Re-invoke the analyses on all operations in the block. 38c095afcbSMogball for (DataFlowAnalysis *analysis : subscribers) 39c095afcbSMogball for (Operation &op : *block) 40c095afcbSMogball solver->enqueue({&op, analysis}); 41c095afcbSMogball } else if (auto *programPoint = point.dyn_cast<GenericProgramPoint *>()) { 42c095afcbSMogball // Re-invoke the analysis on the successor block. 43c095afcbSMogball if (auto *edge = dyn_cast<CFGEdge>(programPoint)) { 44c095afcbSMogball for (DataFlowAnalysis *analysis : subscribers) 45c095afcbSMogball solver->enqueue({edge->getTo(), analysis}); 46c095afcbSMogball } 47c095afcbSMogball } 48c095afcbSMogball } 49c095afcbSMogball 50c095afcbSMogball //===----------------------------------------------------------------------===// 51c095afcbSMogball // PredecessorState 52c095afcbSMogball //===----------------------------------------------------------------------===// 53c095afcbSMogball 54c095afcbSMogball void PredecessorState::print(raw_ostream &os) const { 55c095afcbSMogball if (allPredecessorsKnown()) 56c095afcbSMogball os << "(all) "; 57c095afcbSMogball os << "predecessors:\n"; 58c095afcbSMogball for (Operation *op : getKnownPredecessors()) 59c095afcbSMogball os << " " << *op << "\n"; 60c095afcbSMogball } 61c095afcbSMogball 62*9432fbfeSMogball ChangeResult PredecessorState::join(Operation *predecessor) { 63*9432fbfeSMogball return knownPredecessors.insert(predecessor) ? ChangeResult::Change 64*9432fbfeSMogball : ChangeResult::NoChange; 65*9432fbfeSMogball } 66*9432fbfeSMogball 67*9432fbfeSMogball ChangeResult PredecessorState::join(Operation *predecessor, ValueRange inputs) { 68*9432fbfeSMogball ChangeResult result = join(predecessor); 69*9432fbfeSMogball if (!inputs.empty()) { 70*9432fbfeSMogball ValueRange &curInputs = successorInputs[predecessor]; 71*9432fbfeSMogball if (curInputs != inputs) { 72*9432fbfeSMogball curInputs = inputs; 73*9432fbfeSMogball result |= ChangeResult::Change; 74*9432fbfeSMogball } 75*9432fbfeSMogball } 76*9432fbfeSMogball return result; 77*9432fbfeSMogball } 78*9432fbfeSMogball 79c095afcbSMogball //===----------------------------------------------------------------------===// 80c095afcbSMogball // CFGEdge 81c095afcbSMogball //===----------------------------------------------------------------------===// 82c095afcbSMogball 83c095afcbSMogball Location CFGEdge::getLoc() const { 84c095afcbSMogball return FusedLoc::get( 85c095afcbSMogball getFrom()->getParent()->getContext(), 86c095afcbSMogball {getFrom()->getParent()->getLoc(), getTo()->getParent()->getLoc()}); 87c095afcbSMogball } 88c095afcbSMogball 89c095afcbSMogball void CFGEdge::print(raw_ostream &os) const { 90c095afcbSMogball getFrom()->print(os); 91c095afcbSMogball os << "\n -> \n"; 92c095afcbSMogball getTo()->print(os); 93c095afcbSMogball } 94c095afcbSMogball 95c095afcbSMogball //===----------------------------------------------------------------------===// 96c095afcbSMogball // DeadCodeAnalysis 97c095afcbSMogball //===----------------------------------------------------------------------===// 98c095afcbSMogball 99c095afcbSMogball DeadCodeAnalysis::DeadCodeAnalysis(DataFlowSolver &solver) 100c095afcbSMogball : DataFlowAnalysis(solver) { 101c095afcbSMogball registerPointKind<CFGEdge>(); 102c095afcbSMogball } 103c095afcbSMogball 104c095afcbSMogball LogicalResult DeadCodeAnalysis::initialize(Operation *top) { 105c095afcbSMogball // Mark the top-level blocks as executable. 106c095afcbSMogball for (Region ®ion : top->getRegions()) { 107c095afcbSMogball if (region.empty()) 108c095afcbSMogball continue; 109c095afcbSMogball auto *state = getOrCreate<Executable>(®ion.front()); 110c095afcbSMogball propagateIfChanged(state, state->setToLive()); 111c095afcbSMogball } 112c095afcbSMogball 113c095afcbSMogball // Mark as overdefined the predecessors of symbol callables with potentially 114c095afcbSMogball // unknown predecessors. 115c095afcbSMogball initializeSymbolCallables(top); 116c095afcbSMogball 117c095afcbSMogball return initializeRecursively(top); 118c095afcbSMogball } 119c095afcbSMogball 120c095afcbSMogball void DeadCodeAnalysis::initializeSymbolCallables(Operation *top) { 121c095afcbSMogball auto walkFn = [&](Operation *symTable, bool allUsesVisible) { 122c095afcbSMogball Region &symbolTableRegion = symTable->getRegion(0); 123c095afcbSMogball Block *symbolTableBlock = &symbolTableRegion.front(); 124c095afcbSMogball 125c095afcbSMogball bool foundSymbolCallable = false; 126c095afcbSMogball for (auto callable : symbolTableBlock->getOps<CallableOpInterface>()) { 127c095afcbSMogball Region *callableRegion = callable.getCallableRegion(); 128c095afcbSMogball if (!callableRegion) 129c095afcbSMogball continue; 130c095afcbSMogball auto symbol = dyn_cast<SymbolOpInterface>(callable.getOperation()); 131c095afcbSMogball if (!symbol) 132c095afcbSMogball continue; 133c095afcbSMogball 134c095afcbSMogball // Public symbol callables or those for which we can't see all uses have 135c095afcbSMogball // potentially unknown callsites. 136c095afcbSMogball if (symbol.isPublic() || (!allUsesVisible && symbol.isNested())) { 137c095afcbSMogball auto *state = getOrCreate<PredecessorState>(callable); 138c095afcbSMogball propagateIfChanged(state, state->setHasUnknownPredecessors()); 139c095afcbSMogball } 140c095afcbSMogball foundSymbolCallable = true; 141c095afcbSMogball } 142c095afcbSMogball 143c095afcbSMogball // Exit early if no eligible symbol callables were found in the table. 144c095afcbSMogball if (!foundSymbolCallable) 145c095afcbSMogball return; 146c095afcbSMogball 147c095afcbSMogball // Walk the symbol table to check for non-call uses of symbols. 148c095afcbSMogball Optional<SymbolTable::UseRange> uses = 149c095afcbSMogball SymbolTable::getSymbolUses(&symbolTableRegion); 150c095afcbSMogball if (!uses) { 151c095afcbSMogball // If we couldn't gather the symbol uses, conservatively assume that 152c095afcbSMogball // we can't track information for any nested symbols. 153c095afcbSMogball return top->walk([&](CallableOpInterface callable) { 154c095afcbSMogball auto *state = getOrCreate<PredecessorState>(callable); 155c095afcbSMogball propagateIfChanged(state, state->setHasUnknownPredecessors()); 156c095afcbSMogball }); 157c095afcbSMogball } 158c095afcbSMogball 159c095afcbSMogball for (const SymbolTable::SymbolUse &use : *uses) { 160c095afcbSMogball if (isa<CallOpInterface>(use.getUser())) 161c095afcbSMogball continue; 162c095afcbSMogball // If a callable symbol has a non-call use, then we can't be guaranteed to 163c095afcbSMogball // know all callsites. 164c095afcbSMogball Operation *symbol = symbolTable.lookupSymbolIn(top, use.getSymbolRef()); 165c095afcbSMogball auto *state = getOrCreate<PredecessorState>(symbol); 166c095afcbSMogball propagateIfChanged(state, state->setHasUnknownPredecessors()); 167c095afcbSMogball } 168c095afcbSMogball }; 169c095afcbSMogball SymbolTable::walkSymbolTables(top, /*allSymUsesVisible=*/!top->getBlock(), 170c095afcbSMogball walkFn); 171c095afcbSMogball } 172c095afcbSMogball 173c095afcbSMogball LogicalResult DeadCodeAnalysis::initializeRecursively(Operation *op) { 174c095afcbSMogball // Initialize the analysis by visiting every op with control-flow semantics. 175c095afcbSMogball if (op->getNumRegions() || op->getNumSuccessors() || 176c095afcbSMogball op->hasTrait<OpTrait::IsTerminator>() || isa<CallOpInterface>(op)) { 177c095afcbSMogball // When the liveness of the parent block changes, make sure to re-invoke the 178c095afcbSMogball // analysis on the op. 179c095afcbSMogball if (op->getBlock()) 180c095afcbSMogball getOrCreate<Executable>(op->getBlock())->blockContentSubscribe(this); 181c095afcbSMogball // Visit the op. 182c095afcbSMogball if (failed(visit(op))) 183c095afcbSMogball return failure(); 184c095afcbSMogball } 185c095afcbSMogball // Recurse on nested operations. 186c095afcbSMogball for (Region ®ion : op->getRegions()) 187c095afcbSMogball for (Operation &op : region.getOps()) 188c095afcbSMogball if (failed(initializeRecursively(&op))) 189c095afcbSMogball return failure(); 190c095afcbSMogball return success(); 191c095afcbSMogball } 192c095afcbSMogball 193c095afcbSMogball void DeadCodeAnalysis::markEdgeLive(Block *from, Block *to) { 194c095afcbSMogball auto *state = getOrCreate<Executable>(to); 195c095afcbSMogball propagateIfChanged(state, state->setToLive()); 196c095afcbSMogball auto *edgeState = getOrCreate<Executable>(getProgramPoint<CFGEdge>(from, to)); 197c095afcbSMogball propagateIfChanged(edgeState, edgeState->setToLive()); 198c095afcbSMogball } 199c095afcbSMogball 200c095afcbSMogball void DeadCodeAnalysis::markEntryBlocksLive(Operation *op) { 201c095afcbSMogball for (Region ®ion : op->getRegions()) { 202c095afcbSMogball if (region.empty()) 203c095afcbSMogball continue; 204c095afcbSMogball auto *state = getOrCreate<Executable>(®ion.front()); 205c095afcbSMogball propagateIfChanged(state, state->setToLive()); 206c095afcbSMogball } 207c095afcbSMogball } 208c095afcbSMogball 209c095afcbSMogball LogicalResult DeadCodeAnalysis::visit(ProgramPoint point) { 210c095afcbSMogball if (point.is<Block *>()) 211c095afcbSMogball return success(); 212c095afcbSMogball auto *op = point.dyn_cast<Operation *>(); 213c095afcbSMogball if (!op) 214c095afcbSMogball return emitError(point.getLoc(), "unknown program point kind"); 215c095afcbSMogball 216c095afcbSMogball // If the parent block is not executable, there is nothing to do. 217c095afcbSMogball if (!getOrCreate<Executable>(op->getBlock())->isLive()) 218c095afcbSMogball return success(); 219c095afcbSMogball 220c095afcbSMogball // We have a live call op. Add this as a live predecessor of the callee. 221c095afcbSMogball if (auto call = dyn_cast<CallOpInterface>(op)) 222c095afcbSMogball visitCallOperation(call); 223c095afcbSMogball 224c095afcbSMogball // Visit the regions. 225c095afcbSMogball if (op->getNumRegions()) { 226c095afcbSMogball // Check if we can reason about the region control-flow. 227c095afcbSMogball if (auto branch = dyn_cast<RegionBranchOpInterface>(op)) { 228c095afcbSMogball visitRegionBranchOperation(branch); 229c095afcbSMogball 230c095afcbSMogball // Check if this is a callable operation. 231c095afcbSMogball } else if (auto callable = dyn_cast<CallableOpInterface>(op)) { 232c095afcbSMogball const auto *callsites = getOrCreateFor<PredecessorState>(op, callable); 233c095afcbSMogball 234c095afcbSMogball // If the callsites could not be resolved or are known to be non-empty, 235c095afcbSMogball // mark the callable as executable. 236c095afcbSMogball if (!callsites->allPredecessorsKnown() || 237c095afcbSMogball !callsites->getKnownPredecessors().empty()) 238c095afcbSMogball markEntryBlocksLive(callable); 239c095afcbSMogball 240c095afcbSMogball // Otherwise, conservatively mark all entry blocks as executable. 241c095afcbSMogball } else { 242c095afcbSMogball markEntryBlocksLive(op); 243c095afcbSMogball } 244c095afcbSMogball } 245c095afcbSMogball 246c095afcbSMogball if (op->hasTrait<OpTrait::IsTerminator>() && !op->getNumSuccessors()) { 247c095afcbSMogball if (auto branch = dyn_cast<RegionBranchOpInterface>(op->getParentOp())) { 248c095afcbSMogball // Visit the exiting terminator of a region. 249c095afcbSMogball visitRegionTerminator(op, branch); 250c095afcbSMogball } else if (auto callable = 251c095afcbSMogball dyn_cast<CallableOpInterface>(op->getParentOp())) { 252c095afcbSMogball // Visit the exiting terminator of a callable. 253c095afcbSMogball visitCallableTerminator(op, callable); 254c095afcbSMogball } 255c095afcbSMogball } 256c095afcbSMogball // Visit the successors. 257c095afcbSMogball if (op->getNumSuccessors()) { 258c095afcbSMogball // Check if we can reason about the control-flow. 259c095afcbSMogball if (auto branch = dyn_cast<BranchOpInterface>(op)) { 260c095afcbSMogball visitBranchOperation(branch); 261c095afcbSMogball 262c095afcbSMogball // Otherwise, conservatively mark all successors as exectuable. 263c095afcbSMogball } else { 264c095afcbSMogball for (Block *successor : op->getSuccessors()) 265c095afcbSMogball markEdgeLive(op->getBlock(), successor); 266c095afcbSMogball } 267c095afcbSMogball } 268c095afcbSMogball 269c095afcbSMogball return success(); 270c095afcbSMogball } 271c095afcbSMogball 272c095afcbSMogball void DeadCodeAnalysis::visitCallOperation(CallOpInterface call) { 273c095afcbSMogball Operation *callableOp = nullptr; 274c095afcbSMogball if (Value callableValue = call.getCallableForCallee().dyn_cast<Value>()) 275c095afcbSMogball callableOp = callableValue.getDefiningOp(); 276c095afcbSMogball else 277c095afcbSMogball callableOp = call.resolveCallable(&symbolTable); 278c095afcbSMogball 279c095afcbSMogball // A call to a externally-defined callable has unknown predecessors. 280c095afcbSMogball const auto isExternalCallable = [](Operation *op) { 281c095afcbSMogball if (auto callable = dyn_cast<CallableOpInterface>(op)) 282c095afcbSMogball return !callable.getCallableRegion(); 283c095afcbSMogball return false; 284c095afcbSMogball }; 285c095afcbSMogball 286c095afcbSMogball // TODO: Add support for non-symbol callables when necessary. If the 287c095afcbSMogball // callable has non-call uses we would mark as having reached pessimistic 288c095afcbSMogball // fixpoint, otherwise allow for propagating the return values out. 289c095afcbSMogball if (isa_and_nonnull<SymbolOpInterface>(callableOp) && 290c095afcbSMogball !isExternalCallable(callableOp)) { 291c095afcbSMogball // Add the live callsite. 292c095afcbSMogball auto *callsites = getOrCreate<PredecessorState>(callableOp); 293c095afcbSMogball propagateIfChanged(callsites, callsites->join(call)); 294c095afcbSMogball } else { 295c095afcbSMogball // Mark this call op's predecessors as overdefined. 296c095afcbSMogball auto *predecessors = getOrCreate<PredecessorState>(call); 297c095afcbSMogball propagateIfChanged(predecessors, predecessors->setHasUnknownPredecessors()); 298c095afcbSMogball } 299c095afcbSMogball } 300c095afcbSMogball 301c095afcbSMogball /// Get the constant values of the operands of an operation. If any of the 302c095afcbSMogball /// constant value lattices are uninitialized, return none to indicate the 303c095afcbSMogball /// analysis should bail out. 304c095afcbSMogball static Optional<SmallVector<Attribute>> getOperandValuesImpl( 305c095afcbSMogball Operation *op, 306c095afcbSMogball function_ref<const Lattice<ConstantValue> *(Value)> getLattice) { 307c095afcbSMogball SmallVector<Attribute> operands; 308c095afcbSMogball operands.reserve(op->getNumOperands()); 309c095afcbSMogball for (Value operand : op->getOperands()) { 310c095afcbSMogball const Lattice<ConstantValue> *cv = getLattice(operand); 311c095afcbSMogball // If any of the operands' values are uninitialized, bail out. 312c095afcbSMogball if (cv->isUninitialized()) 313c095afcbSMogball return {}; 314c095afcbSMogball operands.push_back(cv->getValue().getConstantValue()); 315c095afcbSMogball } 316c095afcbSMogball return operands; 317c095afcbSMogball } 318c095afcbSMogball 319c095afcbSMogball Optional<SmallVector<Attribute>> 320c095afcbSMogball DeadCodeAnalysis::getOperandValues(Operation *op) { 321c095afcbSMogball return getOperandValuesImpl(op, [&](Value value) { 322c095afcbSMogball auto *lattice = getOrCreate<Lattice<ConstantValue>>(value); 323c095afcbSMogball lattice->useDefSubscribe(this); 324c095afcbSMogball return lattice; 325c095afcbSMogball }); 326c095afcbSMogball } 327c095afcbSMogball 328c095afcbSMogball void DeadCodeAnalysis::visitBranchOperation(BranchOpInterface branch) { 329c095afcbSMogball // Try to deduce a single successor for the branch. 330c095afcbSMogball Optional<SmallVector<Attribute>> operands = getOperandValues(branch); 331c095afcbSMogball if (!operands) 332c095afcbSMogball return; 333c095afcbSMogball 334c095afcbSMogball if (Block *successor = branch.getSuccessorForOperands(*operands)) { 335c095afcbSMogball markEdgeLive(branch->getBlock(), successor); 336c095afcbSMogball } else { 337c095afcbSMogball // Otherwise, mark all successors as executable and outgoing edges. 338c095afcbSMogball for (Block *successor : branch->getSuccessors()) 339c095afcbSMogball markEdgeLive(branch->getBlock(), successor); 340c095afcbSMogball } 341c095afcbSMogball } 342c095afcbSMogball 343c095afcbSMogball void DeadCodeAnalysis::visitRegionBranchOperation( 344c095afcbSMogball RegionBranchOpInterface branch) { 345c095afcbSMogball // Try to deduce which regions are executable. 346c095afcbSMogball Optional<SmallVector<Attribute>> operands = getOperandValues(branch); 347c095afcbSMogball if (!operands) 348c095afcbSMogball return; 349c095afcbSMogball 350c095afcbSMogball SmallVector<RegionSuccessor> successors; 351c095afcbSMogball branch.getSuccessorRegions(/*index=*/{}, *operands, successors); 352c095afcbSMogball for (const RegionSuccessor &successor : successors) { 353*9432fbfeSMogball // The successor can be either an entry block or the parent operation. 354*9432fbfeSMogball ProgramPoint point = successor.getSuccessor() 355*9432fbfeSMogball ? &successor.getSuccessor()->front() 356*9432fbfeSMogball : ProgramPoint(branch); 357c095afcbSMogball // Mark the entry block as executable. 358*9432fbfeSMogball auto *state = getOrCreate<Executable>(point); 359c095afcbSMogball propagateIfChanged(state, state->setToLive()); 360c095afcbSMogball // Add the parent op as a predecessor. 361*9432fbfeSMogball auto *predecessors = getOrCreate<PredecessorState>(point); 362*9432fbfeSMogball propagateIfChanged( 363*9432fbfeSMogball predecessors, 364*9432fbfeSMogball predecessors->join(branch, successor.getSuccessorInputs())); 365c095afcbSMogball } 366c095afcbSMogball } 367c095afcbSMogball 368c095afcbSMogball void DeadCodeAnalysis::visitRegionTerminator(Operation *op, 369c095afcbSMogball RegionBranchOpInterface branch) { 370c095afcbSMogball Optional<SmallVector<Attribute>> operands = getOperandValues(op); 371c095afcbSMogball if (!operands) 372c095afcbSMogball return; 373c095afcbSMogball 374c095afcbSMogball SmallVector<RegionSuccessor> successors; 375c095afcbSMogball branch.getSuccessorRegions(op->getParentRegion()->getRegionNumber(), 376c095afcbSMogball *operands, successors); 377c095afcbSMogball 378c095afcbSMogball // Mark successor region entry blocks as executable and add this op to the 379c095afcbSMogball // list of predecessors. 380c095afcbSMogball for (const RegionSuccessor &successor : successors) { 381c095afcbSMogball PredecessorState *predecessors; 382c095afcbSMogball if (Region *region = successor.getSuccessor()) { 383c095afcbSMogball auto *state = getOrCreate<Executable>(®ion->front()); 384c095afcbSMogball propagateIfChanged(state, state->setToLive()); 385c095afcbSMogball predecessors = getOrCreate<PredecessorState>(®ion->front()); 386c095afcbSMogball } else { 387c095afcbSMogball // Add this terminator as a predecessor to the parent op. 388c095afcbSMogball predecessors = getOrCreate<PredecessorState>(branch); 389c095afcbSMogball } 390*9432fbfeSMogball propagateIfChanged(predecessors, 391*9432fbfeSMogball predecessors->join(op, successor.getSuccessorInputs())); 392c095afcbSMogball } 393c095afcbSMogball } 394c095afcbSMogball 395c095afcbSMogball void DeadCodeAnalysis::visitCallableTerminator(Operation *op, 396c095afcbSMogball CallableOpInterface callable) { 397c095afcbSMogball // If there are no exiting values, we have nothing to do. 398c095afcbSMogball if (op->getNumOperands() == 0) 399c095afcbSMogball return; 400c095afcbSMogball 401c095afcbSMogball // Add as predecessors to all callsites this return op. 402c095afcbSMogball auto *callsites = getOrCreateFor<PredecessorState>(op, callable); 403c095afcbSMogball bool canResolve = op->hasTrait<OpTrait::ReturnLike>(); 404c095afcbSMogball for (Operation *predecessor : callsites->getKnownPredecessors()) { 405c095afcbSMogball assert(isa<CallOpInterface>(predecessor)); 406c095afcbSMogball auto *predecessors = getOrCreate<PredecessorState>(predecessor); 407c095afcbSMogball if (canResolve) { 408c095afcbSMogball propagateIfChanged(predecessors, predecessors->join(op)); 409c095afcbSMogball } else { 410c095afcbSMogball // If the terminator is not a return-like, then conservatively assume we 411c095afcbSMogball // can't resolve the predecessor. 412c095afcbSMogball propagateIfChanged(predecessors, 413c095afcbSMogball predecessors->setHasUnknownPredecessors()); 414c095afcbSMogball } 415c095afcbSMogball } 416c095afcbSMogball } 417