LCOV - code coverage report
Current view: top level - buildbot/coverage/build/src/theory/bv/abstract - abstraction_module.cpp (source / functions) Hit Total Coverage
Test: coverage.info Lines: 132 136 97.1 %
Date: 2026-09-11 09:47:37 Functions: 9 9 100.0 %
Branches: 111 166 66.9 %

           Branch data     Line data    Source code
       1                 :            : /******************************************************************************
       2                 :            :  * This file is part of the cvc5 project.
       3                 :            :  *
       4                 :            :  * Copyright (c) 2009-2026 by the authors listed in the file AUTHORS
       5                 :            :  * in the top-level source directory and their institutional affiliations.
       6                 :            :  * All rights reserved.  See the file COPYING in the top-level source
       7                 :            :  * directory for licensing information.
       8                 :            :  * ****************************************************************************
       9                 :            :  *
      10                 :            :  * The bit-vector arithmetic abstraction module.
      11                 :            :  */
      12                 :            : 
      13                 :            : #include "theory/bv/abstract/abstraction_module.h"
      14                 :            : 
      15                 :            : #include <algorithm>
      16                 :            : 
      17                 :            : #include "expr/node_manager.h"
      18                 :            : #include "options/bv_options.h"
      19                 :            : #include "smt/env.h"
      20                 :            : #include "theory/bv/theory_bv.h"
      21                 :            : #include "theory/bv/theory_bv_utils.h"
      22                 :            : 
      23                 :            : namespace cvc5::internal {
      24                 :            : namespace theory {
      25                 :            : namespace bv {
      26                 :            : namespace abstract {
      27                 :            : 
      28                 :         44 : AbstractionModule::AbstractionModule(Env& env, TheoryBV* bv)
      29                 :            :     : EnvObj(env),
      30                 :         44 :       d_bv(bv),
      31                 :         88 :       d_absSize(options().bv.bvAbstractionSize),
      32                 :         44 :       d_valLimiter(options().bv.bvAbstractionValueLimiter),
      33                 :         44 :       d_lemmas(nodeManager()),
      34                 :         88 :       d_stats(statisticsRegistry())
      35                 :            : {
      36                 :         44 : }
      37                 :            : 
      38                 :         44 : AbstractionModule::Statistics::Statistics(StatisticsRegistry& reg)
      39                 :            :     : d_numAbstractions(
      40                 :         44 :           reg.registerInt("theory::bv::abstraction::numAbstractions")),
      41                 :         44 :       d_numChecks(reg.registerInt("theory::bv::abstraction::numChecks")),
      42                 :            :       d_numLemmasTier12(
      43                 :         44 :           reg.registerInt("theory::bv::abstraction::numLemmasTier12")),
      44                 :            :       d_numLemmasTier3(
      45                 :         44 :           reg.registerInt("theory::bv::abstraction::numLemmasTier3")),
      46                 :            :       d_numLemmasTier4(
      47                 :         44 :           reg.registerInt("theory::bv::abstraction::numLemmasTier4"))
      48                 :            : {
      49                 :         44 : }
      50                 :            : 
      51                 :      17161 : bool AbstractionModule::abstractable(TNode n) const
      52                 :            : {
      53                 :      17161 :   Kind k = n.getKind();
      54 [ +  + ][ +  + ]:      17161 :   if (k != Kind::BITVECTOR_MULT && k != Kind::BITVECTOR_UDIV
      55         [ +  + ]:      16661 :       && k != Kind::BITVECTOR_UREM)
      56                 :            :   {
      57                 :      16557 :     return false;
      58                 :            :   }
      59                 :            :   // The lemma schemes are binary; cvc5 allows n-ary BITVECTOR_MULT.
      60         [ -  + ]:        604 :   if (n.getNumChildren() != 2)
      61                 :            :   {
      62                 :          0 :     return false;
      63                 :            :   }
      64                 :        604 :   return utils::getSize(n) >= d_absSize;
      65                 :            : }
      66                 :            : 
      67                 :         36 : Node AbstractionModule::abstractNode(TNode node)
      68                 :            : {
      69 [ -  + ][ -  + ]:         36 :   Assert(abstractable(node));
                 [ -  - ]
      70                 :         36 :   auto it = d_cache.find(node);
      71                 :            :   // If the cached node is different from `node`, it must be an abstraction
      72                 :            :   // constant. Hence, we don't have to check for d_abs2node.find(it->second).
      73 [ +  + ][ -  + ]:         36 :   if (it != d_cache.end() && !it->second.isNull() && it->second != node)
         [ -  - ][ -  + ]
      74                 :            :   {
      75                 :          0 :     return it->second;
      76                 :            :   }
      77                 :            :   // A fresh, opaque constant of the same sort. It is deliberately *not* a
      78                 :            :   // purification skolem: the abstraction must stay an unconstrained
      79                 :            :   // over-approximation, never silently re-expanded to `op` during rewriting
      80                 :            :   // or model construction.
      81                 :         72 :   Node t = NodeManager::mkDummySkolem("bvabs", node.getType());
      82                 :         36 :   d_abs2node.emplace(t, node);
      83                 :            :   // Note: We do not insert into d_cache here: the caller writes the mapping
      84                 :            :   // through a live iterator, which an insertion could invalidate (rehash).
      85                 :         36 :   ++d_stats.d_numAbstractions;
      86                 :         36 :   return t;
      87                 :         36 : }
      88                 :            : 
      89                 :    2887055 : Node AbstractionModule::abstract(TNode fact)
      90                 :            : {
      91                 :    2887055 :   NodeManager* nm = nodeManager();
      92                 :   11548220 :   std::vector<TNode> visit{fact};
      93                 :            :   do
      94                 :            :   {
      95                 :    2920228 :     TNode cur = visit.back();
      96                 :    2920228 :     auto it = d_cache.find(cur);
      97         [ +  + ]:    2920228 :     if (it == d_cache.end())
      98                 :            :     {
      99                 :            :       // Do not descend into terms of other theories (e.g., array selects).
     100                 :            :       // The bit-blaster treats them as opaque leaves (variables), and they are
     101                 :            :       // the terms shared with the other theory. Rebuilding below such a term
     102                 :            :       // would create a NEW node (e.g., a select over an abstracted index)
     103                 :            :       // distinct from the shared one: the other theory would continue to
     104                 :            :       // reason about the original while this solver constrains the copy,
     105                 :            :       // silently disconnecting the two (unsound under theory combination).
     106                 :      17072 :       theory::TheoryId tid = d_env.theoryOf(cur);
     107         [ +  + ]:      33113 :       if (cur.getNumChildren() > 0 && tid != theory::THEORY_BV
     108 [ +  + ][ +  + ]:      33113 :           && tid != theory::THEORY_BOOL)
                 [ +  + ]
     109                 :            :       {
     110                 :       5043 :         d_cache.emplace(cur, cur);
     111                 :       5043 :         visit.pop_back();
     112                 :       5043 :         continue;
     113                 :            :       }
     114                 :      12029 :       d_cache.emplace(cur, Node::null());
     115                 :      12029 :       visit.insert(visit.end(), cur.begin(), cur.end());
     116                 :      12029 :       continue;
     117                 :      12029 :     }
     118         [ +  + ]:    2903156 :     if (it->second.isNull())
     119                 :            :     {
     120                 :      12029 :       bool rebuild = false;
     121                 :      12029 :       std::vector<Node> children;
     122         [ +  + ]:      12029 :       if (cur.getMetaKind() == kind::metakind::PARAMETERIZED)
     123                 :            :       {
     124                 :        196 :         children.push_back(cur.getOperator());
     125                 :            :       }
     126         [ +  + ]:      33173 :       for (const TNode& child : cur)
     127                 :            :       {
     128                 :      21144 :         Node abs = d_cache.at(child);
     129 [ -  + ][ -  + ]:      21144 :         Assert(!abs.isNull());
                 [ -  - ]
     130 [ +  + ][ +  + ]:      21144 :         rebuild = rebuild || abs != child;
     131                 :      21144 :         children.push_back(abs);
     132                 :      21144 :       }
     133         [ +  + ]:      12029 :       Node ret = rebuild ? nm->mkNode(cur.getKind(), children) : Node(cur);
     134         [ +  + ]:      12029 :       if (abstractable(ret))
     135                 :            :       {
     136                 :         36 :         ret = abstractNode(ret);
     137                 :            :       }
     138                 :      12029 :       it->second = rewrite(ret);
     139                 :      12029 :     }
     140                 :    2903156 :     visit.pop_back();
     141 [ +  + ][ +  + ]:    5840456 :   } while (!visit.empty());
     142                 :    8661165 :   return d_cache.at(fact);
     143                 :    2887055 : }
     144                 :            : 
     145                 :        334 : void AbstractionModule::check(std::vector<Node>& lemmas)
     146                 :            : {
     147                 :        334 :   ++d_stats.d_numChecks;
     148                 :        334 :   NodeManager* nm = nodeManager();
     149                 :        334 :   Node falseNode = nm->mkConst(false);
     150                 :        334 :   std::vector<Node> args(3);
     151                 :        334 :   std::vector<Node> vals(3);
     152         [ +  + ]:        721 :   for (const auto& [t, n] : d_abs2node)
     153                 :            :   {
     154 [ -  + ][ -  + ]:        387 :     Assert(abstractable(n));
                 [ -  - ]
     155                 :        387 :     Kind kind = n.getKind();
     156                 :        387 :     TNode x = n[0];
     157                 :        387 :     TNode s = n[1];
     158                 :        387 :     Node xval = d_bv->getValue(x);
     159                 :        387 :     Node sval = d_bv->getValue(s);
     160                 :        387 :     Node tval = d_bv->getValue(t);
     161 [ +  - ][ +  - ]:        387 :     Assert(xval.isConst() && sval.isConst() && tval.isConst());
         [ +  - ][ +  - ]
         [ -  + ][ -  + ]
                 [ -  - ]
     162                 :            : 
     163                 :            :     // The abstraction `t = op(x, s)` is consistent with the model iff the
     164                 :            :     // actual operator applied to the operand values equals the value of `t`.
     165                 :            :     // If so, there is nothing to refine for this node.
     166                 :        774 :     Node value = rewrite(nm->mkNode(kind, xval, sval));
     167         [ +  + ]:        387 :     if (value == tval)
     168                 :            :     {
     169                 :        151 :       continue;
     170                 :            :     }
     171                 :            : 
     172                 :            :     // Tier 1/2: add the FIRST Table-2 lemma scheme (in registry order, which
     173                 :            :     // follows Bitwuzla's) that is violated under the current model, i.e.,
     174                 :            :     // whose instantiation constant-folds to false when x, s, t are substituted
     175                 :            :     // by their model values. Only one lemma is added per term per refinement
     176                 :            :     // round (as in Bitwuzla), so the SAT solver is not swamped with all
     177                 :            :     // violated schemes at once.
     178                 :            :     //
     179                 :            :     // Note: We do not use the Evaluator here since it only substitutes
     180                 :            :     //       *variable* keys (it matches `args` entries only for nodes with
     181                 :            :     //       isVar()) and leaves a compound key unsubstituted (thus would
     182                 :            :     //       evaluate to a non-constant, and so miss the violation).
     183 [ +  + ][ -  - ]:        944 :     args = {x, s, t};
     184 [ +  + ][ -  - ]:        944 :     vals = {xval, sval, tval};
     185                 :        236 :     bool violated = false;
     186         [ +  + ]:       1724 :     for (const std::unique_ptr<AbstractionLemma>& lemma : d_lemmas.lemmas(kind))
     187                 :            :     {
     188                 :       3320 :       Node inst = lemma->instance(x, s, t);
     189         [ +  + ]:       1660 :       if (inst.isNull())
     190                 :            :       {
     191                 :        315 :         inst = lemma->instance(x, s, t, xval, sval);
     192                 :            :       }
     193         [ +  + ]:       1660 :       if (inst.isNull())
     194                 :            :       {
     195                 :            :         // Value lemma not applicable under the current model values (e.g. the
     196                 :            :         // POW2 schemes when the value is not a power of two).
     197                 :        180 :         continue;
     198                 :            :       }
     199                 :            :       Node subst =
     200                 :       1480 :           inst.substitute(args.begin(), args.end(), vals.begin(), vals.end());
     201         [ +  + ]:       1480 :       if (rewrite(subst) == falseNode)
     202                 :            :       {
     203                 :        172 :         lemmas.push_back(inst);
     204                 :        172 :         violated = true;
     205                 :        172 :         break;
     206                 :            :       }
     207 [ +  + ][ +  + ]:       1832 :     }
                    [ + ]
     208                 :            :     // If a Table-2 lemma ruled out this spurious model, move on.
     209         [ +  + ]:        236 :     if (violated)
     210                 :            :     {
     211                 :        172 :       ++d_stats.d_numLemmasTier12;
     212                 :        172 :       continue;
     213                 :            :     }
     214                 :            : 
     215                 :            :     // No tier-1/2 lemma violated, fall back to value instantiation if we have
     216                 :            :     // not exhausted the instantiation budget for this term yet.
     217                 :         64 :     uint64_t budget = utils::getSize(t) / d_valLimiter;
     218         [ +  + ]:         64 :     if (d_valueInstCount[t] < budget)
     219                 :            :     {
     220                 :            :       // Tier 3: value instantiation.
     221                 :         48 :       lemmas.push_back(
     222 [ +  + ][ -  - ]:        240 :           nm->mkNode(Kind::IMPLIES,
     223 [ +  + ][ -  - ]:        192 :                      {nm->mkNode(Kind::AND, {x.eqNode(xval), s.eqNode(sval)}),
     224                 :         96 :                       t.eqNode(value)}));
     225                 :         48 :       ++d_valueInstCount[t];
     226                 :         48 :       ++d_stats.d_numLemmasTier3;
     227                 :            :     }
     228                 :            :     else
     229                 :            :     {
     230                 :            :       // Tier 4: bit-blasting fallback. Assert t = op(x, s), forcing the real
     231                 :            :       // circuit to be bit-blasted; `t` is fully constrained from now on.
     232                 :         16 :       lemmas.push_back(t.eqNode(nm->mkNode(kind, x, s)));
     233                 :         16 :       ++d_stats.d_numLemmasTier4;
     234                 :            :     }
     235 [ +  + ][ +  + ]:       2002 :   }
         [ +  + ][ +  + ]
         [ +  + ][ +  + ]
     236                 :        334 : }
     237                 :            : 
     238                 :            : #ifdef CVC5_ASSERTIONS
     239                 :        110 : bool AbstractionModule::isModelConsistent()
     240                 :            : {
     241                 :        110 :   NodeManager* nm = nodeManager();
     242         [ +  + ]:        207 :   for (const auto& [t, n] : d_abs2node)
     243                 :            :   {
     244 [ -  + ][ -  + ]:         97 :     Assert(abstractable(n));
                 [ -  - ]
     245                 :        194 :     Node xval = d_bv->getValue(n[0]);
     246                 :        194 :     Node sval = d_bv->getValue(n[1]);
     247                 :         97 :     Node tval = d_bv->getValue(t);
     248 [ +  - ][ +  - ]:         97 :     if (!xval.isConst() || !sval.isConst() || !tval.isConst())
         [ -  + ][ -  + ]
     249                 :            :     {
     250                 :          0 :       continue;
     251                 :            :     }
     252         [ -  + ]:         97 :     if (rewrite(nm->mkNode(n.getKind(), xval, sval)) != tval)
     253                 :            :     {
     254                 :          0 :       return false;
     255                 :            :     }
     256 [ +  - ][ -  + ]:         97 :   }
         [ -  - ][ +  - ]
                    [ - ]
     257                 :        110 :   return true;
     258                 :            : }
     259                 :            : #endif
     260                 :            : 
     261                 :         74 : bool AbstractionModule::isAbstracted(TNode node) const
     262                 :            : {
     263                 :         74 :   auto it = d_cache.find(node);
     264                 :            :   // Note: The cache also holds null visit markers (while abstract() runs) and
     265                 :            :   //       rebuilt terms for nodes above an abstracted subterm. Hence, we also
     266                 :            :   //       have to check if there exists a mapping in d_abs2node.
     267 [ +  - ][ +  - ]:        138 :   return it != d_cache.end() && !it->second.isNull() && it->second != node
     268 [ +  + ][ +  - ]:        138 :          && d_abs2node.find(it->second) != d_abs2node.end();
     269                 :            : }
     270                 :            : 
     271                 :         32 : TNode AbstractionModule::getAbstraction(TNode node) const
     272                 :            : {
     273 [ -  + ][ -  + ]:         32 :   Assert(isAbstracted(node));
                 [ -  - ]
     274                 :         64 :   return d_cache.at(node);
     275                 :            : }
     276                 :            : 
     277                 :            : }  // namespace abstract
     278                 :            : }  // namespace bv
     279                 :            : }  // namespace theory
     280                 :            : }  // namespace cvc5::internal

Generated by: LCOV version 1.14