diff options
| author | Julian Andres Klode <julian.klode@canonical.com> | 2024-08-01 12:43:09 +0900 |
|---|---|---|
| committer | Julian Andres Klode <julian.klode@canonical.com> | 2024-11-02 10:23:15 +0100 |
| commit | 761fc96fe6158d46f11137c14822545ac0b0fe91 (patch) | |
| tree | be1f908a2b22b13a368cf2bcfe70709bb8794ddd /apt-pkg/solver3.cc | |
| parent | 09e853dd424a6ab53bd5791de75894a985968399 (diff) | |
solver3: Track all assignments and undo them individually
Adopt MiniSAT's strategy for dealing with assignments and choices,
having a single step undoOne() function to undo one and record them
all on the queue; this should likely speed up backtracking since we
no longer need to rescan everything.
Diffstat (limited to 'apt-pkg/solver3.cc')
| -rw-r--r-- | apt-pkg/solver3.cc | 127 |
1 files changed, 71 insertions, 56 deletions
diff --git a/apt-pkg/solver3.cc b/apt-pkg/solver3.cc index 2ba6f6065..c18420d71 100644 --- a/apt-pkg/solver3.cc +++ b/apt-pkg/solver3.cc @@ -169,8 +169,7 @@ APT::Solver::Solver(pkgCache &cache, pkgDepCache::Policy &policy) verStates(cache.Head().VersionCount), pkgObsolete(cache.Head().PackageCount) { - static_assert(sizeof(APT::Solver::State<pkgCache::PkgIterator>) == 3 * sizeof(int)); - static_assert(sizeof(APT::Solver::State<pkgCache::VerIterator>) == 3 * sizeof(int)); + static_assert(sizeof(APT::Solver::State) == 3 * sizeof(int)); static_assert(sizeof(APT::Solver::Reason) == sizeof(map_pointer<pkgCache::Package>)); static_assert(sizeof(APT::Solver::Reason) == sizeof(map_pointer<pkgCache::Version>)); } @@ -333,6 +332,7 @@ bool APT::Solver::Install(pkgCache::PkgIterator Pkg, Reason reason, Group group) if (unlikely(debug >= 1)) std::cerr << "[" << depth() << "] Install:" << Pkg.FullName() << " (" << WhyStr(reason) << ")\n"; (*this)[Pkg] = {reason, depth(), Decision::MUST}; + solved.push_back(Solved{Reason(Pkg), std::nullopt}); // Insert the work item. Work workItem{Reason(Pkg), depth(), group}; @@ -383,8 +383,12 @@ bool APT::Solver::Install(pkgCache::VerIterator Ver, Reason reason, Group group) if (unlikely(debug >= 1)) std::cerr << "[" << depth() << "] Install:" << Ver.ParentPkg().FullName() << "=" << Ver.VerStr() << " (" << WhyStr(reason) << ")\n"; (*this)[Ver] = {reason, depth(), Decision::MUST}; + solved.push_back(Solved{Reason(Ver), std::nullopt}); if ((*this)[Ver.ParentPkg()].decision != Decision::MUST) + { (*this)[Ver.ParentPkg()] = {Reason(Ver), depth(), Decision::MUST}; + solved.push_back(Solved{Reason(Ver.ParentPkg()), std::nullopt}); + } for (auto OV = Ver.ParentPkg().VersionList(); not OV.end(); ++OV) { @@ -422,6 +426,7 @@ bool APT::Solver::Reject(pkgCache::PkgIterator Pkg, Reason reason, Group group) if (unlikely(debug >= 1)) std::cerr << "[" << depth() << "] Reject:" << Pkg.FullName() << " (" << WhyStr(reason) << ")\n"; (*this)[Pkg] = {reason, depth(), Decision::MUSTNOT}; + solved.push_back(Solved{Reason(Pkg), std::nullopt}); for (auto ver = Pkg.VersionList(); not ver.end(); ver++) if (not Reject(ver, Reason(Pkg), group)) return false; @@ -450,6 +455,7 @@ bool APT::Solver::Reject(pkgCache::VerIterator Ver, Reason reason, Group group) if (unlikely(debug >= 1)) std::cerr << "[" << depth() << "] Reject:" << Ver.ParentPkg().FullName() << "=" << Ver.VerStr() << " (" << WhyStr(reason) << ")\n"; (*this)[Ver] = {reason, depth(), Decision::MUSTNOT}; + solved.push_back(Solved{Reason(Ver), std::nullopt}); if (auto pkg = Ver.ParentPkg(); (*this)[pkg].decision != Decision::MUSTNOT) { bool anyInstallable = false; @@ -471,7 +477,10 @@ bool APT::Solver::Reject(pkgCache::VerIterator Ver, Reason reason, Group group) (Ver.ParentPkg().FullName() + "=" + Ver.VerStr()).c_str()); } else if ((*this)[Ver.ParentPkg()].decision != Decision::MUSTNOT) // Last installable invalidated + { (*this)[Ver.ParentPkg()] = {Reason(Ver), depth(), Decision::MUSTNOT}; + solved.push_back(Solved{Reason(Ver), std::nullopt}); + } } if (not RejectReverseDependencies(Ver)) @@ -729,15 +738,55 @@ void APT::Solver::Push(Work work) work.Dump(cache); std::cerr << "\n"; } - choices.push_back(std::move(work)); + + choices.push_back(solved.size()); + solved.push_back(Solved{Reason(), std::move(work)}); // Pop() will call MergeWithStack() when reverting to level 0, or RevertToStack after dumping to the debug log. _error->PushToStack(); } +void APT::Solver::UndoOne() +{ + auto solvedItem = solved.back(); + + if (unlikely(debug >= 4)) + std::cerr << "Undoing a single decision\n"; + + if (not solvedItem.assigned.empty()) + { + if (unlikely(debug >= 4)) + { + if (auto P = solvedItem.assigned.Pkg(cache); not P.end()) + std::cerr << "Unassign " << P.FullName() << "\n"; + if (auto V = solvedItem.assigned.Ver(cache); not V.end()) + std::cerr << "Unassign " << V.ParentPkg().FullName() << "=" << V.VerStr() << "\n"; + } + auto &state = (*this)[solvedItem.assigned]; + state.decision = Decision::NONE; + state.reason = Reason(); + state.depth = 0; + } + + if (auto work = solvedItem.work) + { + if (unlikely(debug >= 4)) + { + std::cerr << "Adding work item "; + work->Dump(cache); + std::cerr << "\n"; + } + + AddWork(std::move(*work)); + } + + solved.pop_back(); + + // FIXME: Add the undo handling here once we have watchers. +} + bool APT::Solver::Pop() { - auto depth = APT::Solver::depth(); - if (depth == 0) + if (depth() == 0) return false; if (unlikely(debug >= 2)) @@ -746,64 +795,30 @@ bool APT::Solver::Pop() _error->RevertToStack(); - depth--; - - // Clean up the higher level states. - // FIXME: Do not override the hints here. - for (auto &state : pkgStates) - if (state.depth > depth) - state = {}; - for (auto &state : verStates) - if (state.depth > depth) - state = {}; - - // This destroys the invariants that `work` must be a heap. But this is ok: - // we are restoring the invariant below, because rejecting a package always - // calls std::make_heap. - work.erase(std::remove_if(work.begin(), work.end(), [depth](Work &w) -> bool - { return w.depth > depth || w.dirty; }), + assert(choices.back() < solved.size()); + int itemsToUndo = solved.size() - choices.back(); + pkgCache::VerIterator choice(cache, solved[choices.back()].work->choice); + + for (; itemsToUndo; --itemsToUndo) + UndoOne(); + + // We need to remove any work that is at a higher depth. + choices.pop_back(); + work.erase(std::remove_if(work.begin(), work.end(), [this](Work &w) -> bool + { return w.depth > depth() || w.dirty; }), work.end()); std::make_heap(work.begin(), work.end()); - // Go over the solved items, see if any of them need to be moved back or deleted. - solved.erase(std::remove_if(solved.begin(), solved.end(), [this, depth](Work &w) -> bool - { - if (w.depth > depth) // Deeper decision level is no longer valid. - return true; - // This item is still solved, keep it on the solved list. - if (std::any_of(w.solutions.begin(), w.solutions.end(), [this](auto ver) - { return (*this)[ver].decision == Decision::MUST; })) - return false; - // We are not longer solved, move it back to work. - AddWork(std::move(w)); - return true; }), - solved.end()); - - Work w = std::move(choices.back()); - choices.pop_back(); if (unlikely(debug >= 2)) - { - std::cerr << "Backtracking to choice "; - w.Dump(cache); - std::cerr << "\n"; - } - if (unlikely(debug >= 4)) - { - std::cerr << "choices: "; - for (auto &i : choices) - { - std::cerr << pkgCache::VerIterator(cache, i.choice).ParentPkg().FullName(true) << "=" << pkgCache::VerIterator(cache, i.choice).VerStr(); - } - std::cerr << std::endl; - } + std::cerr << "Backtracking to choice " << choice.ParentPkg().FullName() << "=" << choice.VerStr() << "\n"; - assert(w.choice != nullptr); // FIXME: There should be a reason! - if (not Reject(pkgCache::VerIterator(cache, w.choice), {}, Group::HoldOrDelete)) + if (not Reject(choice, {}, Group::HoldOrDelete)) return false; - w.choice = nullptr; - AddWork(std::move(w)); + if (unlikely(debug >= 2)) + std::cerr << "Backtracked to choice " << choice.ParentPkg().FullName() << "=" << choice.VerStr() << "\n"; + return true; } @@ -879,7 +894,7 @@ bool APT::Solver::Solve() auto item = std::move(work.back()); work.pop_back(); - solved.push_back(item); + solved.push_back(Solved{Reason(), item}); if (std::any_of(item.solutions.begin(), item.solutions.end(), [this](auto ver) { return (*this)[ver].decision == Decision::MUST; })) |
