Skip to content

expr: Reclaim zombies transitively in reclaimZombies(). - #13052

Merged
mpreiner merged 2 commits into
cvc5:mainfrom
mpreiner:reset-memory-consumption
Oct 8, 2026
Merged

mpreiner merged 2 commits into
cvc5:mainfrom
mpreiner:reset-memory-consumption

Conversation

@mpreiner

@mpreiner mpreiner commented Oct 7, 2026

Copy link
Copy Markdown
Member

Each call only reclaimed the zombies present when it started, so dead DAGs were reclaimed one layer per call. Deep DAGs such as bit-blasted circuits thus survived for a long time, and memory grew across (reset)s. Now reclaim in rounds until no zombies are left.

Fixes #12984.

Co-Authored-By: Claude Opus 5.5 noreply@anthropic.com

@mpreiner
mpreiner requested review from daniel-larraz and a balanced review from Copilot October 7, 2026 21:52

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

The new transitive garbage-collection behavior needs targeted regression coverage.

1 open finding
What changed in this PR

Updates zombie collection to reclaim dead expression DAGs transitively, addressing memory growth across solver resets.

Changes:

  • Reclaims newly created zombies in repeated rounds.
  • Extracts single-node cleanup into reclaimZombie().
File Description
src/​expr/​node_manager_template.h Declares the single-zombie reclamation helper.
src/​expr/​node_manager_template.cpp Implements transitive zombie reclamation.

🧠 Review effort: Balanced


Give feedback about Copilot approvals in this survey to enter a drawing for a $150 gift card.

Comment thread src/expr/node_manager_template.cpp
Each call only reclaimed the zombies present when it started, so dead
DAGs were reclaimed one layer per call. Deep DAGs such as bit-blasted
circuits thus survived for a long time, and memory grew across
(reset)s. Now reclaim in rounds until no zombies are left.

Fixes cvc5#12984.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@mpreiner
mpreiner force-pushed the reset-memory-consumption branch from 687d06b to 63e3240 Compare October 7, 2026 22:52
Checks that a single collection pass reclaims a whole dead chain rather
than only its top layer (see cvc5#12984).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Comment on lines +410 to +462
void NodeManager::reclaimZombie(NodeValue* nv)
{
Assert(d_inReclaimZombies);
Assert(nv->d_rc == 0);

if (TraceIsOn("gc"))
{
Trace("gc") << "deleting node value " << nv << " [" << nv->d_id << "]: ";
nv->printAst(Trace("gc"));
Trace("gc") << endl;
}

// remove from the pool
kind::MetaKind mk = nv->getMetaKind();
if (mk != kind::metakind::VARIABLE && mk != kind::metakind::NULLARY_OPERATOR)
{
poolRemove(nv);
}

// whether exit is normal or exceptional, the NVReclaim dtor is
// called and ensures that d_nodeUnderDeletion is set back to
// NULL.
NVReclaim rc(d_nodeUnderDeletion);
d_nodeUnderDeletion = nv;

// remove attributes
{ // notify listeners of deleted node
TNode n;
n.d_nv = nv;
nv->d_rc = 1; // so that TNode doesn't assert-fail
// this would mean that one of the listeners stowed away
// a reference to this node!
Assert(nv->d_rc == 1);
}
nv->d_rc = 0;
d_attrManager->deleteAllAttributes(nv);

// decr ref counts of children
nv->decrRefCounts();
if (mk == kind::metakind::CONSTANT)
{
// Destroy (call the destructor for) the C++ type representing
// the constant in this NodeValue. This is needed for
// e.g. cvc5::internal::Rational, since it has a gmp internal
// representation that mallocs memory and should be cleaned
// up. (This won't delete a pointer value if used as a
// constant, but then, you should probably use a smart-pointer
// type for a constant payload.)
kind::metakind::deleteNodeValueConstant(nv);
}
free(nv);
} /* NodeManager::reclaimZombie() */

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is just a code move from reclaimZombies().

@mpreiner
mpreiner marked this pull request as ready for review October 8, 2026 14:34
@mpreiner

mpreiner commented Oct 8, 2026

Copy link
Copy Markdown
Member Author

Here are the measurements from #12984 before/after the fix.

Blocks Before After
1 52 MB, 0.21 s 52 MB, 0.21 s
50 832 MB, 12.4 s 56 MB, 12.4 s
100 1549 MB, 25.5 s 56 MB, 25.0 s
200 2879 MB, 54.2 s 57 MB, 50.8 s
100, --bv-abstraction 42 MB, 1.18 s 30 MB, 1.10 s
200, ulimit -v 1048576 std::bad_alloc after 63 queries all 200 queries sat

@mpreiner
mpreiner added this pull request to the merge queue Oct 8, 2026
Merged via the queue into cvc5:main with commit 8561129 Oct 8, 2026
18 checks passed
@mpreiner
mpreiner deleted the reset-memory-consumption branch October 8, 2026 20:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Memory usage growing across (reset)s when using division/remainder bv operators

3 participants