Two bit-vector operations which are more tricky to translate to integer arithmetic are the extraction of bits from a larger bit-vector, and the concatenation of two bit-vectors. This is formalised using the function \(\textsf {bvextract}_{[u, l]}\), which cuts out a slice of \(u-l+1\) consecutive bits, and the function \(\textsf {bvconcat}_{v+w}\) forming a bit-vector of length \(v+w\). We call the fragment of bit-vector logic containing only these operations, and positive equalities, the structural fragment of bit-vector theory.
Example 7
Consider the following bit-vectors:
$$\begin{aligned} c_1&= [0,0] = 0&c_2&= [1,1] = 3&c_3&= [0,1] = 1\\ c_4&= [0,0,1,1] = 3&c_5&= [1,1,0,0] = 12 \end{aligned}$$
where a bit-sequence \([b_n, \dots b_0]\) is represented by the number \(\sum _{i=0}^{n} b_i2^i\). The following equations hold between those bit-vectors:
$$\begin{aligned} \textsf {bvconcat}_{2+2}(c_1,c_2)&= c_4&\textsf {bvconcat}_{2+2}(c_2,c_1)&= c_5 \\ \textsf {bvextract}_{[3, 2]}(c_4)&= c_1&\textsf {bvextract}_{[1, 0]}(c_4)&= c_2\\ \textsf {bvextract}_{[2, 1]}(c_4)&= c_3~. \end{aligned}$$
While it is possible to translate extractions and concatenations to integer arithmetic, it is not always efficient. We show here that it can be more efficient to keep extractions abstract and only convert at need. This approach can also help to compute simpler interpolants.
Example 8
We consider the example from [19], a formula over the structural fragment, which we divide into two parts
$$\begin{aligned} A&=~ (x[5:0] = z \wedge z[5:2] = 11)\\ B&=~ (y[7:2] = 6 \wedge x = y) \end{aligned}$$
where the bit-vectors x, y are of width 8, and z is of width 6. The unsatisfiability of \(A \wedge B\) could be proved, as before, by translating the bit-vector constraints to our core language:
$$\begin{aligned} A_{\text {core}}&=~~ in _{8}(x) \wedge in _{6}(z) \wedge ubmod_{6} (x, z) \wedge \exists c_1 .\; ( in _{2}(c_1) \wedge z = 11\cdot 2^2 + c_1)\\ B_{\text {core}}&=~~ in _{8}(x) \wedge in _{8}(y) \wedge \exists c_2 .\; ( in _{2}(c_2) \wedge y = 6\cdot 2^2 + c_2) \wedge x = y \end{aligned}$$
Using the interpolation procedure presented in Sect. 4, we can compute the following interpolant:
$$\begin{aligned} I =~~ \exists c .\; (x = 44 + 64c \vee x = 45 + 64c \vee x = 46 + 64c \vee x = 46 + 64c) \end{aligned}$$
The conjunction is unsatisfiable due to the conflicting assignment of a small slice in x and y, as illustrated in Fig. 6. However, when translating to integer arithmetic the overall structure is lost, and the interpolant contains a lot of redundancy. The situation gets worse with increasing bit-widths. Consider a formula where the width of x, y are doubled, while the widths of the extractions are kept constant:
$$\begin{aligned} A'&=~~ (x[13:8] = z \wedge z[5:2] = 11)\\ B'&=~~ (y[15:10] = 6 \wedge x = y) \end{aligned}$$
The same procedure now yields an interpolant of exponentially greater size:
$$\begin{aligned} I' =~~ \exists c .\; (x = 4097 + 8192c \vee x = 4098 + 8192c \vee \dots \vee x = 5120 + 8192c) \end{aligned}$$
We will explain in the next sections how more succinct interpolants can be computed by eliminating the extraction operation only lazily.
The structural fragment
In [19], a polynomial fragment of the bit-vector theory is identified, consisting of formulas that only contain extractions, concatenations, and positive equalities. The satisfiability of formulas in the structural fragment is decidable in polynomial time by a congruence closure procedure over decomposed bit-vectors [18].
Definition 1
The structural fragment consists of bit-vector formulas of the form \(\phi _1 \wedge \phi _2 \wedge \dots \wedge \phi _n\) where each \(\phi _i\) is an equation constructed using bit-vector variables, concrete bit-vectors, and the operators \(\textsf {bvextract}_{[u, l]}\) and \(\textsf {bvconcat}_{v+w}\).
Note that a formula containing \(\textsf {bvconcat}_{v+w}\) can be translated into an equi-satisfiable formula that only uses \(\textsf {bvextract}_{[u, l]}\). We illustrate the translation with an example, and introduce the formal rule in Fig. 7. Consider the formula \(\phi [\textsf {bvconcat}_{v+w}(s, t)]\) containing the concatenation of two bit-vectors. The concatenation can be eliminated by introducing an existentially quantified variable to represent the result of the concatenation; the relationship with the arguments is established using extraction terms:
$$\begin{aligned} \exists x .\; ( in _{v+w}(x) \wedge \textsf {bvextract}_{[v+w-1, w]}(x) = s \wedge \textsf {bvextract}_{[w-1, 0]}(x) = t \wedge \phi [x]) \end{aligned}$$
To define a formal calculus for the structural fragment, we introduce an extended core language by adding a further family \({P_{ex} = \{extr_l^u \mid u, l \in {\mathbb {N}}, u \ge l\}}\) of predicates representing extraction from bit-vectors. Semantically, \(extr_l^u(s,t)\) relates s and t if t is the result of extracting the bits u through l from s. This is formally expressed as the existence of two bit-vectors x, y such that t can be made equal to s by prepending x and appending y:
$$\begin{aligned} extr_l^u(s, t) ~\Leftrightarrow ~ in _{u - l + 1}(t) \wedge \exists x, y.\; ( in _{l}(y) \wedge s = x \cdot 2^{u+1} + t \cdot 2^l + y) \end{aligned}$$
Note that the argument s is not bounded, which implies that the definition contains a bound for the lower slice y, but not for x.
The rewriting rules in Fig. 7 define how extraction and concatenation are translated to the \(P_{ex}\) predicates, following the same schema as in Sect. 4. As a side-effect of adding \(\textsf {bvextract}_{[u, l]}\) and \(\textsf {bvconcat}_{w+v}\), and moving beyond the structural fragment, the calculus can also reason about the bit-wise operators \(\mathsf {BVOP}_{\mathsf {bv}}= \{\mathsf {bvnot}, \mathsf {bvand}, \mathsf {bvor}\), \(\mathsf {bvxor}\}\), by extracting the individual bits of the operands and encoding the Boolean semantics using inequalities. The rewriting rules for such an encoding are given Fig. 7 as well, and are also used in our implementation.
Bit-vector decomposition
As demonstrated in Sect. 1.2, unsatisfiability of formulas can sometimes be proven by just focusing on the right slice of bit-vectors; the challenge lies in how to decompose the bit-vectors to find the conflicts. Intuitively, there is no need to split apart bits which are never constrained individually. We follow the procedure described in [19], and use the notion of cut points for this reason. Cut points of a bit-vector variable determine the slices that need to be considered, and the points at which the bit-vectors might have to be decomposed, and are determine by the boundaries of extraction operations.
More formally, given a formula \(\phi \) in the structural fragment over set \(S = C \cup X\) of constants and variables, a cut point configuration is a function \(\mathcal{C} : S \rightarrow \mathcal{P}(\mathbb {N})\) satisfying the following properties:
-
for each \(extr_l^u(s, t)\) literal, it is the case that:
-
\(\{l, u+1\} \subseteq \mathcal {C}(s)\), and
-
\(\{ i - l \mid i \in \mathcal {C}(s) \text {~with~} l \le i \le u+1 \} = \{ i \in \mathcal {C}(t) \mid i \le u - l +1\}\).
-
for each equality \(s = t\) it is the case that \(\mathcal {C}(s) = \mathcal {C}(t)\).
The set of cut points for all bit-vectors can be obtained by a fix-point computation, which begins with all cut points from extractions, and then propagates using the equalities until all conditions hold.
Example 9
Translated to our extended core language, the constraints from Example 8 are:
$$\begin{aligned} A'_{\text {core}}&=~~ in _{8}(x) \wedge in _{6}(z) \wedge extr_0^5(x,z) \wedge extr_2^5(z,11)\\ B'_{\text {core}}&=~~ in _{8}(x) \wedge in _{8}(y) \wedge extr_2^7(y,6) \wedge x = y \end{aligned}$$
The extraction literals induce immediate cut points for x, y and z, respectively: \(\{6, 0\}, \{8, 2\}\), and \(\{6, 2\}\). Since x and z are related by the literal \(extr_0^5(x,z)\), the cut point 2 needs to be added to the set for x as well; similarly, due to the equation \(x = y\), also the cut points \(\{6, 0\}\) have to be added for y, and 8 for x. The alignment of the bit-vectors is illustrated in Fig. 8, and the complete sets of cut points after propagation are \(\{8, 6, 2, 0\}\), \(\{8, 6, 2, 0\}\), and \(\{6, 2, 0\}\). If the bit-vectors x, y, z are decomposed according to their cut points, then simple reasoning reveals the inconsistency between \(extr_2^5(x,5)\), \(extr_2^5(y, 6)\), and \(x = y\).
An interpolating calculus for extractions
A decomposition according to the cut points yields a complete and polynomial procedure for the structural fragment [19]. We formalise the splitting of bit-vector extractions with an interpolating calculus rule extr-split in Fig. 9. Intuitively, we cut the bit-vector s at point i and introduce two existential variables \(x_1, x_2\) corresponding to the two slices. By constraining the corresponding slices of r according to the decomposition, we ensure the original equality between the extraction of s and r. The rule can be generalised to split into several slices at once, allowing for shorter proofs.
After extracts have been split at the cut points, we rely on congruence closure to take care of \(extr_l^u\) literals, in a similar way as the procedure in [18]. Congruence closure is in our setting expressed by an axiom schema for functional consistency of the \(extr_l^u\) predicates:
$$\begin{aligned} \forall s_1, s_2, r_1, r_2 .\; (extr_l^u(s_1, r_1) \wedge extr_l^u(s_2, r_2) \wedge s_1 = s_2 \rightarrow r_1 = r_2) \end{aligned}$$
(2)
The axiom states that two extr-literals will yield the same result if the first arguments coincide, and if the same bits are extracted. Similar axioms for predicate consistency and functional consistency are used in [4]. To simplify presentation, we model the instantiation of (2) using the calculus rule extr-cc in Fig. 9; the figure also gives four interpolating versions of the rule, for the four possible combinations of L/R-labels. The LR/RL rules differ in the label used in their premises. The rules can be derived by instantiating (2) using either \(\forall \) -left\(_L\) or \(\forall \) -left\(_R\), in a similar way as in Sect. 3.3. In practice, and in our implementation, the calculus rules are used as in classical SMT-style congruence closure: they are triggered when the first arguments of a pair of extr-literals have become equal.
Extraction operations can also be translated to arithmetic, which is needed to evaluate extraction from concrete numbers, and when constructing proofs for formulas that are not in the structural fragment (i.e., that combine extraction with other bit-vector operations). The rule extr-arith encodes an operation \(extr_l^u(s, r)\) using a modulo constraint to eliminate bits above position u, and division to strip away bits below position l. We express the division by existentially quantifying the remainder. A more direct rule to perform evaluation is introduced in Sect. 5.4.
Example 10
We continue Example 9, and show how an interpolating proof can be constructed for the conjunction \(A'_{\text {core}} \wedge B'_{\text {core}}\). The root sequent of the proof is \({\lfloor A'_{\text {core}}\rfloor _L, \lfloor B'_{\text {core}}\rfloor _R}\;\vdash \;{\emptyset }\). In the proof tree shown in Fig. 10, we first split the given formulas using Boolean rules. Then, the literal \(extr_0^5(x, z)\) can be decomposed using our extr-split rule at the cut point 2, and congruence closure is applied to the literals \(extr_2^5(z, c_1)\) and \(extr_2^5(z, 11)\). We similarly decompose the literal \(extr_2^7(y, 6)\) at cut point 6, and then use extr-arith to reduce \(extr_0^3(6, c_2)\) to \(c_2 = 6\). Finally, we need a second application of congruence closure, \({\textsc {extr-cc}}_{LR}\), to relate the extractions from x and y.
The resulting interpolant is the formula \(\exists c . (extr_2^5(x,c) \wedge c = 11)\). The quantifier in the formula stems from the application of \({\textsc {extr-cc}}_{LR}\), which transfers the local symbol \(c_1\) from L to R, and thus makes it shared. A quantifier is needed to eliminate the symbol again from the interpolant. However, as can be seen the quantifier naturally disappears when translating the interpolant back to functional notation, which yields the formula \(\textsf {bvextract}_{[5, 2]}(x) = 11\) in the structural fragment.
It is quite easy to see that our calculus is sound and complete for formulas in the structural fragment. The calculus does not guarantee, however, that interpolants computed for formulas in the structural fragment are again in the structural fragment, or that interpolants are quantifier-free. This leads to the question whether the structural fragment is actually closed under interpolation, i.e., whether every unsatisfiability conjunction has an interpolant that is again in the fragment. The answer to this question is positive, and it turns out that our calculus can also be used to compute such interpolants, if the right strategy is used to construct proofs.
Theorem 1
The structural fragment is closed under interpolation.
Proof
We give a simple proof that follows from the fact that our calculus can model bit-blasting of bit-vector formulas, and builds on work on EUF interpolation in [3, 4]. The existence of more compact interpolants, avoiding the need to blast to individual bits, can also be shown, but this is slightly more involved.
Suppose \(A \wedge B\) is an unsatisfiable conjunction in the structural fragment, and \(A_{\text {core}} \wedge B_{\text {core}}\) the translation to the (extended) core language. Further assume that \(x_1, \ldots , x_m\) are the shared bit-vector constants of A, B, and that \(w_1, \ldots , w_m\) are their bit-widths, respectively. This means that we are searching for an interpolant in the structural fragment that may only contain the constants \(x_1, \ldots , x_m\). To model bit-blasting, we augment \(A_{\text {core}}, B_{\text {core}}\) by adding fresh names \(\{b_i^j,c_i^j\}_{i, j}\) for the individual bits of \(x_1, \ldots , x_m\):
$$\begin{aligned} A'_{\text {core}} =~~&A_{\text {core}} \wedge \bigwedge _{\begin{array}{c} i \in \{1, \ldots , m\} \\ j \in \{0, \ldots , w_i - 1\} \end{array}} in _{1}(b_i^j) \wedge extr_j^j(x_i, b_i^j)\\ B'_{\text {core}} =~~&B_{\text {core}} \wedge \bigwedge _{\begin{array}{c} i \in \{1, \ldots , m\} \\ j \in \{0, \ldots , w_i - 1\} \end{array}} in _{1}(c_i^j) \wedge extr_j^j(x_i, c_i^j) \end{aligned}$$
This means that \(b_i^j/c_i^j\) is the name of the jth bit of \(x_i\) in the L/R partition. Note that a formula \(I_{\text {core}}\) is an interpolant of \(A_{\text {core}} \wedge B_{\text {core}}\) iff it is an interpolant of \(A'_{\text {core}} \wedge B'_{\text {core}}\), because no shared symbols are added, and we can therefore work with the latter conjunction.
Without loss of generality, we further assume that even every local constant in \(A'_{\text {core}}\) and \(B'_{\text {core}}\) occurs as first argument of some \(extr_l^u\)-literal, and that every bit in such constants is extracted by some of the literals. This assumption can be ensured by adding further literals to the formulas, without changing the set of possible interpolants.
We then construct a proof for the root sequent \({\lfloor A'_{\text {core}}\rfloor _L, \lfloor B'_{\text {core}}\rfloor _R}\;\vdash \;{\emptyset }\) in our interpolating calculus by systematically applying the calculus rules. Rules are applied likewise to the L- and the R-formulas, so that we only mention the L-versions at this point for sake of brevity:
-
\(\wedge {\textsc {-left}}_L\) to split conjunctions, and \(\exists {\textsc {-left}}_L\) to eliminate quantifiers;
-
\({\textsc {extr-split}}_L\) to split every occurring extr predicate down to the level of individual bits;
-
\({\textsc {extr-cc}}_{LL}\) whenever two extracts \(\lfloor extr_j^j(s, r)\rfloor _L, \lfloor extr_j^j(s, r')\rfloor _L\) for the same bit occur, followed by arithmetic rules to close the left premise; this generates an equation \(\lfloor r = r'\rfloor _L\);
-
\({\textsc {extr-cc}}_{LL}\) whenever two extracts \(\lfloor extr_j^j(s, r)\rfloor _L, \lfloor extr_j^j(t, r')\rfloor _L\) in combination with an equation \(\lfloor s = t\rfloor _L\) occur, followed by an application of \({\textsc {close}}_{LL}\) to close the left premise; again, this generates an equation \(\lfloor r = r'\rfloor _L\);
-
\({\textsc {extr-arith}}_L\) whenever an extract \(\lfloor extr_j^j(\alpha , r)\rfloor _L\) from a concrete number \(\alpha \in \mathbb {Z}\) occurs, followed by arithmetic rules to simplify the generated formula to an equation \(\lfloor r = 0\rfloor _L\) or \(\lfloor r = 1\rfloor _L\);
-
\({\textsc {extr-arith}}_L\) whenever an extract \(\lfloor extr_0^0(s, r)\rfloor _L\) in combination with the domain constraint \( in _{1}(s)\) occurs, i.e., when the single bit of a bit-vector of width 1 is extracted, followed by arithmetic rules to simplify the generated formula to an equation \(\lfloor r = s\rfloor _L\).
After those rule applications, we can focus on the obtained bit-level equations in the proof goal: on equations \(\lfloor s = t\rfloor _{D}\) or \(\lfloor s = \alpha \rfloor _{D}\) in which s, t have width 1 (i.e., \( in _{1}(s)\) and \( in _{1}(t)\)) and \(\alpha \in \{0, 1\}\), with \(D \in \{L, R\}\). By construction, the set of L-labelled bit-level equations is equi-satisfiable to the original formula \(A'_{\text {core}}\), and the R-labelled equations are equi-satisfiable to \(B'_{\text {core}}\), so that we can continue constructing a bit-level proof using the equations.
Since the conjunction \(A'_{\text {core}} \wedge B'_{\text {core}}\) is by assumption unsatisfiable, there are three possible cases:
(i) the equations labelled with L are by themselves unsatisfiable, the interpolant is \( false \), and the proof can be closed by applying arithmetic rules;
(ii) symmetrically, the equations labelled with R are unsatisfiable, and the interpolant is \( true \); or
(iii) the equations are unsatisfiable only in combination.
In case (iii), there has to be a chain of equations, alternating between L- and R-equations, that witnesses unsatisfiability. There are several symmetric cases, of which we only consider one:
$$\begin{aligned} \underbrace{ 0 = \cdots = b_{i_1}^{j_1}}_{L-\text {equations}} \doteq \underbrace{c_{i_1}^{j_1} = \cdots = c_{i_2}^{j_2}}_{R-\text {equations}} \doteq \underbrace{b_{i_2}^{j_2} = \cdots = b_{i_3}^{j_3}}_{L-\text {equations}} \doteq \cdots \doteq \underbrace{ c_{i_k}^{j_k} = \cdots = 1}_{R-\text {equations}} \end{aligned}$$
(3)
In the other cases, the chain can start in R and end in L, or start and end in the same partition. The dotted equations \(b_{i}^{j} \doteq c_{i}^{j}\) are implied by the literal \(\lfloor extr_{j}^{j}(x_{i}, b_{i}^{j})\rfloor _L\), \(\lfloor extr_{j}^{j}(x_{i}, c_{i}^{j})\rfloor _R\), but do not exist explicitly in the proof goal.
From (3), it is easy to read off an interpolant for \(A \wedge B\) in the structural fragment by summarising the L-chains:
$$\begin{aligned} I =~~ \textsf {bvextract}_{[j_1, j_1]}(x_{i_1}) = 0 \wedge \bigwedge _{l=2}^{k-1} \textsf {bvextract}_{[j_l, j_l]}(x_{i_l}) = \textsf {bvextract}_{[j_{l+1}, j_{l+1}]}(x_{i_{l+1}}) \end{aligned}$$
Clearly, I follows from A, and \(I \wedge B\) is unsatisfiable due to (3).
To construct an interpolant mechanically in our calculus, there are several strategies. The simplest one is to apply the interpolating cut-rule \({\textsc {cut}}_{RL}\) to each the formulas \(extr_{j_1}^{j_1}(x_{i_1}, 0)\) and \(\{\, \exists z.\, (extr_{j_l}^{j_l}(x_{i_l}, z) \wedge extr_{j_{l+1}}^{j_{l+1}}(x_{i_{l+1}}, z)) \,\}_{l=2}^{k-1}\), which yields an interpolant \(I_{\text {core}}\) in the core language that corresponds to the structural interpolant I shown above. \(\square \)
A rewriting rule for constant extraction
Given an extraction \(extr_l^u(s, r)\) and bounds on s, it is in some cases possible to determine value of extracted bits. For example, the longest prefix on which the lower and upper bound agree is guaranteed to be present in any consistent value of s. Therefore, extractions that overlap with that prefix yield some bit values of the extraction without knowing the exact value of s. We allow rewriting if the extraction operator falls entirely within the common prefix:
where \( rem \) and \( div \) are the integer remainder and division, respectively. The rule extr-const allows in particular evaluation of extractions from constant bit-vectors.
Splitting of disequalities
As shown above, proofs can be closed by finding contradicting assignments to (a slice of) a bit-vector. In general, formulas can also contain bit-vector disequalities, i.e., negative equalities between bit-vectors. As an optimisation, disequalities can be split using the notion of cut points as well. Given a formula with a disequality \(s \ne t\), we extend the notion of cut point configurations (Sect. 5.2) by also propagating between s and t. For a cut point \(i \in \mathcal {C}(s) = \mathcal {C}(t)\), we can then replace the disequality with a disjunction of two disequalities, as expressed by the following rule:
The constants c, d must be fresh and not occur in the conclusion in this rule.