figure a
figure b

1 Introduction

IC3/PDR  [3] is an efficient SAT-based Model Checking algorithm. Among many other innovations in IC3/PDR is the concept of a modular SAT-solver that divides a formula into multiple frames and each frame is solved by an individual SAT solver. The solvers communicate by exchanging proof obligations (i.e., satisfying assignments) and lemmas (i.e., learned clauses).

While modular reasoning in IC3/PDR is very efficient for a Model Checker, it is not as efficient as a classical monolithic SAT-solver. This is not surprising since modularity restricts the solver to colorable refutations [11], which are, in the worst case, exponentially bigger than unrestricted refutations. On the positive side, IC3/PDR ’s modular SAT-solving makes interpolation trivial, and enables generalizations of proof obligations and inductive generalization of lemmas – both are key to the success of IC3/PDR.

This motivates the study of modular SAT-solving, initiated by SMS  [1]. Our strategic vision is that our study will contribute to improvements in IC3/PDR. However, in this paper, we focus on modular SAT-solving in isolation.

In modular SAT-solving, multiple solvers interact to check satisfiability of a partitioned CNF formula, where each part of the formula is solved by one of the solvers. In this paper, for simplicity, we consider the case of two solvers \(\langle S_{\textsf{s}}, S_{\textsf{m}} \rangle \) checking satisfiability of a formula pair \(\langle \varPhi _\textsf{s}, \varPhi _\textsf{m} \rangle \). \(S_{\textsf{m}} \) is a main solver and \(S_{\textsf{s}} \) is a secondary solver. In the notation, the solvers are written right-to-left to align with IC3/PDR, where the main solver is used for frame 1 and the secondary solver is used for frame 0.

When viewed as a modular SAT-solver, IC3/PDR is uni-directional. First, \(S_{\textsf{m}} \) finds a satisfying assignment \(\sigma \) to \(\varPhi _{\textsf{m}}\) and only then, \(S_{\textsf{s}} \) extends \(\sigma \) to an assignment for \(\varPhi _{\textsf{s}}\). Learned clauses, called lemmas in IC3/PDR, are only shared (or copied) from the secondary solver \(S_{\textsf{s}} \) to the main solver \(S_{\textsf{m}} \).

SAT Modulo SAT (SMS) [1] is a modular SAT-solver that extends IC3/PDR by allowing inter-modular unit propagation and conflict analysis: whenever an interface literal is placed on a trail of any solver, it is shared with the other solver and both solvers run unit propagation, exchanging unit literals. This makes modular SAT-solving in SMS bi-directional as information flows in both directions between the solvers. Bi-directional reasoning can simplify proofs, but it significantly complicates conflict analysis. To manage conflict analysis, SMS does not allow the secondary solver \(S_{\textsf{s}} \) to make any decisions before the main solver \(S_{\textsf{m}} \) is able to find a complete assignment to its clauses. As a result, learned clauses are either local to each solver, or flow only from \(S_{\textsf{s}} \) to \(S_{\textsf{m}} \), restricting the structure of refutations similarly to IC3/PDR.

Both IC3/PDR and SMS require \(S_{\textsf{m}} \) to find a complete satisfying assignment to \(\varPhi _{\textsf{m}}\) before the solving is continued in \(S_{\textsf{s}} \). This is problematic since \(\varPhi _{\textsf{m}}\) might be hard to satisfy, causing them to get stuck in \(\varPhi _{\textsf{m}}\), even if considering both formulas together quickly reveals the (un)satisfiability of \(\langle \varPhi _\textsf{s}, \varPhi _\textsf{m} \rangle \).

In this paper, we introduce specSMS — a modular SAT-solver that employs a truly bi-directional reasoning. specSMS builds on SMS, while facilitating deeper communication between the modules by (1) allowing learnt clauses to flow in both directions, and (2) letting the two solvers interleave their decisions. The key challenge is in the adaptation of conflict analysis to properly handle the case of a conflict that depends on decisions over local variables of both solvers. Such a conflict cannot be explained to either one of the solvers using only interface clauses (i.e., clauses over interface variables). It may, therefore, require backtracking the search without learning any conflict clauses. To address this challenge, specSMS uses speculation, which tames decisions of the secondary solver that are interleaved with decisions of the main solver. If the secondary solver satisfies all of its clauses during speculation, a validation phase is employed, where the main solver attempts to extend the assignment to satisfy its unassigned clauses. If speculation leads to a conflict which depends on local decisions of both solvers, refinement is employed to resolve the conflict. Refinement ensures progress even if no conflict clause can be learnt. With these ingredients, we show that specSMS is sound and complete (i.e., always terminates).

To certify specSMS ’s result when it determines that a formula is unsatisfiable, we extract a modular clausal proof from its execution. To this end, we extend DRUP proofs [12] to account for modular reasoning, and devise a procedure for trimming modular proofs. Such proofs are applicable both to specSMS and to SMS. Finally, we propose an interpolation algorithm that extracts an interpolant [4] from a modular proof. Since clauses are propagated between the solvers in both directions, the extracted interpolants have the shape \(\bigwedge _i (C_i \Rightarrow cls _i\)), where \(C_i\) are conjunctions of clauses and each \( cls _i\) is a clause.

Original SMS is implemented on top of MiniSAT. For this paper, we implemented both SMS and specSMS in Z3 [5], using the extendable SAT-solver interface of Z3. Thanks to its bi-directional reasoning, specSMS is able to efficiently solve both sat and unsat formulas that are provably hard for existing modular SAT-solvers, provided that speculation is performed at the right time. We describe a simple heuristic to decide when to speculate.

In summary, we make the following contributions: (i) the specSMS algorithm that leverages bi-directional modular reasoning (Sec. 3); (ii) modular DRUP proofs for specSMS (Sec. 4.1); (iii) proof-based interpolation algorithm; (iv) heuristics to guide speculation (Sec. 5); and (v) implementation and validation (Sec. 6).

2 Motivating examples

In this section, we discuss two examples in which both IC3/PDR-style uni-directional reasoning and SMS-style shallow bi-directional reasoning are ineffective. The examples illustrate why existing modular reasoning gets stuck. To better convey our intuition, we present our problems at word level using bit-vector variables directly, without explicitly converting them to propositional variables.

Example 1

Consider the following modular sat query: \(\langle \varphi _{ in }, \varphi _{{\text {SHA-1}}}\rangle \), where \(\varphi _{ in } \triangleq ( in = in _1) \vee ( in = in _2)\), \( in \) is a 512-bit vector, \( in _1\), \( in _2\) are 512-bit values, \(\varphi _{{\text {SHA-1}}} \triangleq ({\text {SHA-1}}_{ circ }( in ) = {\text {SHA-1}}_{ in _1}\)), \({\text {SHA-1}}_{ circ }( in )\) is a circuit that computes \({\text {SHA-1}}\) of \( in \), and \({\text {SHA-1}}_{ in _1}\) is the 20 byte \({\text {SHA-1}}\) message digest of \( in _1\).

Checking the satisfiability of \(\varphi _{ in } \wedge \varphi _{{\text {SHA-1}}}\) is easy because it contains both the output and the input of the \({\text {SHA-1}}\) circuit. However, existing modular SAT-solvers attempt to solve the problem starting by finding a complete satisfying assignment to \(\varphi _{{\text {SHA-1}}}\). This is essentially the problem of inverting the \({\text {SHA-1}}\) function, which is known to be very hard for a SAT-solver. The improvements in SMS allow unit propagation between the two modules. However, this does not help since there are no unit clauses in \(\varphi _{ in }\).

On the other hand, specSMS proceeds as follows: (1) when checking satisfiability of \(\varphi _{{\text {SHA-1}}}\), it decides to speculate, (2) it starts checking satisfiability of \(\varphi _{ in }\), branches on variables \( in \), finds an assignment \(\sigma \) to \( in \) and unit propagates \(\sigma \) to \(\varphi _{{\text {SHA-1}}}\), (3) if there is a conflict in \(\varphi _{{\text {SHA-1}}}\), it learns the conflict clause \( in \ne in _2\), and (4) it terminates with a satisfying assignment \( in = in _1\).

Speculation in step (1) is what differentiates specSMS from IC3/PDR and SMS. The specifics of when exactly specSMS speculates is guided by a heuristic that is explained in Sec. 5.

Example 2

Speculation is desirable for unsatisfiable formulas as well. Consider the modular sat query \(\langle \varphi _{+},\varphi _{-}\rangle \), where \(\varphi _{+} \triangleq (a < 0\Rightarrow x) \wedge (a \ge 0\Rightarrow x) \wedge PHP^1_{32}\) and \(\varphi _{-} \triangleq (b < 0\Rightarrow \lnot x) \wedge (b \ge 0\Rightarrow \lnot x) \wedge PHP^2_{32}\). Here, a and b are 32-wide bitvectors and local to the respective modules. \(PHP_{32}\) encodes the problem of fitting 32 pigeons into 31 holes and \(PHP^1_{32}\) and \(PHP^2_{32}\) denote a partitioning of \(PHP_{32}\) into 2 problems such that both formulas contain all variables. The modular problem \(\langle \varphi _{+}, \varphi _{-}\rangle \) is unsatisfiable, x and \(PHP^1_{32}\) being two possible interpolants. IC3/PDR and SMS only find the second interpolant. This is because, all satisfying assignments to \(\varphi _{-}\) immediately produce a conflict in \(PHP^1_{32}\) part of \(\varphi _{+}\), without having to make any decisions. However, learning an interpolant containing x requires searching (i.e., deciding) in both \(\varphi _{+}\) and \(\varphi _{-}\). specSMS solves this problem by speculating right after deciding on all b variables. During speculation, the secondary solver hits a conflict on x once it tries to find an assignment to a variables. Note here that speculating after finding assignments to b variables and before finding an assignment to \(PHP^2_{32}\) is crucial for specSMS to find the small interpolant.

These examples highlight the need to speculate while doing modular reasoning. Even though speculation by itself is quite powerful, to make specSMS effective in practice, we need good heuristics to decide when to enter speculation. We discuss some simple heuristics in Sec. 5.

3 Speculative SAT Modulo SAT

This section presents specSMS — a modular bi-directional SAT algorithm. For simplicity, we restrict our attention to the case of two modules. However, the algorithm easily generalizes to any sequence of modules.

3.1 Sat Modulo Sat

We assume that the reader has some familiarity with internals of a MiniSAT-like SAT solver [6] and with SMS  [1]. We give a brief background on SMS, highlighting some of the key aspects. SMS decides satisfiability of a partitioned CNF formula \(\langle \varPhi _\textsf{s}, \varPhi _\textsf{m} \rangle \) with a set of shared interface variables I. It uses two modules \(\langle S_{\textsf{s}}, S_{\textsf{m}} \rangle \), where \(S_{\textsf{m}} \) is a main module used to solve \(\varPhi _{\textsf{m}}\), and \(S_{\textsf{s}} \) is a secondary module to solve \(\varPhi _{\textsf{s}}\). Each module is a SAT solver (with a slightly extended interface, as described in this section). We refer to them as modules or solvers, interchangeably. Each solver has its own clause database (initialized with \(\varPhi _i\) for \(i \in \{\textsf{m}, \textsf{s} \}\)), and a trail of literals, just as a regular SAT solver. The solvers keep their decision levels in sync. Whenever a decision is made in one solver, the decision level of the other solver is incremented as well (adding a null literal to its trail if necessary). Whenever one solver back-jumps to level i, the other solver back-jumps to level i as well. Assignments to interface variables are shared between the solvers: whenever such a literal is added to the trail of one solver (either as a decision or due to propagation), it is also added to the trail of the other solver. SMS requires that \(S_{\textsf{s}} \) does not make any decisions, until \(S_{\textsf{m}} \) finds a satisfying assignment to its clauses.

Inter-modular propagation and conflict analysis The two key features of SMS are inter-modular unit propagation (called PropagateAll in [1]) and the corresponding inter-modular conflict analysis. In PropagateAll, whenever an interface literal is added to the trail of one solver, it is added to the trail of the other, and both solvers run unit propagation. Whenever a unit literal \(\ell \) is copied from the trail of one solver to the other, the reason for \(\ell \) in the destination solver is marked using a marker \(\text {ext}\). This indicates that the justification for the unit is external to the destination solverFootnote 1. Propagation continues until either there are no more units to propagate or one of the solvers hits a conflict.

Conflict analysis in SMS is extended to account for units with no reason clauses. If such a literal \(\ell \) is used in conflict analysis, its reason is obtained by using \(\textsf {AnalyzeFinal} (\ell )\) on the other solver to compute a clause \((s\Rightarrow \ell )\) over the interface literals. This clause is copied to the requesting solver and is used as the missing reason. Multiple such clauses can be copied (or learned) during analysis of a single conflict clause – one clause for each literal in the conflict that is assigned by the other solver.

In SMS, it is crucial that \(\textsf {AnalyzeFinal} (\ell )\) always succeeds to generate a reason clause over the interface variables. This is ensured by only calling \(\textsf {AnalyzeFinal} (\ell )\) in the \(S_{\textsf{s}} \) solver on literals that were added to the trail when \(S_{\textsf{s}} \) was not yet making decisions. This can happen in one of two scenarios: either \(S_{\textsf{m}} \) hits a conflict due to literals propagated from \(S_{\textsf{s}} \), in which case \(\textsf {AnalyzeFinal} \) is invoked in \(S_{\textsf{s}} \) on each literal marked \(\text {ext}\) in \(S_{\textsf{m}} \) that is involved in the conflict resolution to obtain its reason; or \(S_{\textsf{s}} \) hits a conflict during unit propagation, in which case it invokes \(\textsf {AnalyzeFinal} \) to obtain a conflict clause over the interface variables that blocks the partial assignment of \(S_{\textsf{m}} \). In both cases, new reason clauses are always copied from \(S_{\textsf{s}} \) to \(S_{\textsf{m}} \). We refer the reader to [1] for the pseudo-code of the above inter-modular procedures for details.

3.2 Speculative Sat Modulo Sat

specSMS extends SMS  [1] by a combination of speculation, refinement, and validation. During the search in the main solver \(S_{\textsf{m}} \), specSMS non-deterministically speculates by allowing the secondary solver \(S_{\textsf{s}} \) to extend the current partial assignment of \(\varPhi _{\textsf{m}}\) to a satisfying assignment of \(\varPhi _{\textsf{s}}\). If \(S_{\textsf{s}} \) is unsuccessful (i.e., hits a conflict), and the conflict depends on a combination of a local decision of \(S_{\textsf{m}} \) with some decision of \(S_{\textsf{s}} \), then the search reverts to \(S_{\textsf{m}} \) and its partial assignment is refined by forcing \(S_{\textsf{m}} \) to decide on an interface literal from the conflict. On the other hand, if \(S_{\textsf{s}} \) is successful, solving switches to the main solver \(S_{\textsf{m}} \) that validates the current partial assignment by extending it to all of its clauses. This either succeeds (meaning, \(\langle \varPhi _{\textsf{s}}, \varPhi _{\textsf{m}} \rangle \) is sat), or fails and another refinement is initiated. Note that the two sub-cases where \(S_{\textsf{s}} \) is unsuccessful but the reason for the conflict is either local to \(S_{\textsf{s}} \) or local to \(S_{\textsf{m}} \) are handled as in SMS.

Fig. 1.
figure 1

State transitions of specSMS. A state \(\langle P, D^0\rangle \) means that the secondary solver \(S_{\textsf{s}} \) is in propagate mode and the main solver \(S_{\textsf{m}} \) is in decide mode. Each edge is guarded with a condition. The condition \(S_{\textsf{m}}: \text {SAT}\) means that \(S_{\textsf{m}} \) found a full satisfying assignment to \(\varPhi _{\textsf{m}}\). The condition \(S_{\textsf{m}}: \text {C}\,@\,{\scriptscriptstyle \le }\!j\) means that \(S_{\textsf{m}} \) hit a conflict at a decision level below j. The four states in yellow corresponds to SMS; two states in green are unique to specSMS.

Search modes specSMS controls the behavior of the solvers and their interaction through search modes. Each solver can be in one of the following search modes: Decide, Propagate, and Finished. In Decide, written \(D^{i}\), the solver treats all decisions below level i as assumptions and is allowed to both make decisions and do unit propagation. In Propagate, written P, the solver makes no decisions, but does unit propagation whenever new literals are added to its trail. In Finished, written F, the clause database of the solver is satisfied; the solver neither makes decisions nor propagates unit literals.

The pair of search modes of both modules is called the state of specSMS, where we add a unique state called unsat for the case when the combination of the modules is known to be unsatisfiable. The possible states and transitions of specSMS are shown in Fig. 1. States \( unsat \) and \(\langle F,F \rangle \) are two final states, corresponding to unsat and sat, respectively. In all other states, exactly one of the solvers is in a state \(D^{i}\). We refer to this solver as active. The part of the transition system highlighted in yellow correspond to SMS, and the green part includes the states and transitions that are unique to specSMS. Normal execution with bi-directional propagation specSMS starts in the state \(\langle P, D^0\rangle \), with the main solver being active. In this state, it can proceed like SMS by staying in the yellow region of Fig. 1. We call this normal execution with bi-directional propagation, since (only) unit propagation goes between solvers.

Speculation What sets specSMS apart is speculation: at any non-deterministically chosen decision level i, specSMS can pause deciding on the main solver and activate the secondary solver (i.e., transition to state \(\langle D^i, P\rangle \)). During speculation, only the secondary solver makes decisions. Since the main solver does not have a full satisfying assignment to its clauses, the secondary solver propagates assignments to the main solver and vice-versa.

Speculation terminates when the secondary solver \(S_{\textsf{s}} \) either: (1) hits a conflict that cannot be resolved by inter-modular conflict analysis; (2) hits a conflict below decision level i; or (3) finds a satisfying assignment to \(\varPhi _{\textsf{s}}\).

Case (1) is most interesting, and is what makes specSMS differ from SMS. Note that a conflict clause is not resolved by inter-modular conflict analysis only if it depends on an external literal on the trail of \(S_{\textsf{s}} \) that cannot be explained by an interface clause from \(S_{\textsf{m}} \). This is possible when both \(S_{\textsf{m}} \) and \(S_{\textsf{s}} \) have partial assignments during speculation. So the conflict might depend on the local decisions of \(S_{\textsf{m}} \). This cannot be communicated to \(S_{\textsf{s}} \) using only interface variables.

Refinement In specSMS, this is handled by modifying the Reason method in the solvers to fail (i.e., return \(\text {ext}\)) whenever \(\textsf {AnalyzeFinal} \) returns a non-interface clause. Additionally, the literal on which \(\textsf {AnalyzeFinal} \) failed is recorded in a global variable \( refineLit \). This is shown in Alg. 1. The inter-modular conflict analysis is modified to exit early whenever Reason fails to produce a justification. At this point, specSMS exits speculation, returns to the initial state \(\langle P, D^0 \rangle \), both solvers back-jump to decision level i at which speculation was initiated, and \(S_{\textsf{m}} \) is forced to decide on \( refineLit \).

We call this transition a refinement because the partial assignment of the main solver \(S_{\textsf{m}} \) (which we view as an abstraction) is updated (a.k.a., refined) based on the information that was not available to it (namely, a conflict with a set of decisions in the secondary solver \(S_{\textsf{s}} \)). Since \( refineLit \) was not decided on in \(S_{\textsf{m}} \) prior to speculation, deciding on it is a new decision that ensures progress in \(S_{\textsf{m}} \). The next speculation is possible only under strictly more decisions in \(S_{\textsf{m}} \) than before, or when \(S_{\textsf{m}} \) back-jumps and flips an earlier decision.

We illustrate the refinement process on a simple example:

Example 3

Consider the query \(\langle \varPhi _{\textsf{s}}, \varPhi _{\textsf{m}}\rangle \) with:

figure c

First, \(S_{\textsf{m}}\) decides a (at level 1), which causes no propagations. Then, specSMS enters speculative mode, transitions to \(\langle D^1, P\rangle \) and starts making decisions in \(S_{\textsf{s}}\). \(S_{\textsf{s}}\) decides z and calls PropagateAll. Afterwards, the trails for \(S_{\textsf{m}}\) and \(S_{\textsf{s}}\) are as follows:

\(S_{\textsf{m}}\)

a @ 1

\( null \) @ 2

\(\overline{i}\) (ext)

\(\overline{j}\) (1)

k (2)

\(S_{\textsf{s}}\)

\( null \) @ 1

z @ 2

\(\overline{i}\) (3)

\(\overline{j}\) (ext)

k (ext)

where x @ i denotes that literal x is decided at level i, and \(x\ (r)\) denotes that literal x is propagated using a reason clause r, or due to the other solver (if \(r = \text {ext}\)). A conflict is hit in \(S_{\textsf{s}}\) in clause (4). Inter-modular conflict analysis begins. \(S_{\textsf{s}}\) first asks for the reason for k, which is clause (2) in \(S_{\textsf{m}}\). This clause is copied to \(S_{\textsf{s}}\). Note that unlike SMS, clauses can move from \(S_{\textsf{m}} \) to \(S_{\textsf{s}} \). The new conflict to be analyzed is \((i \vee j \vee j)\). Now the reason for \(\overline{j}\) is asked of \(S_{\textsf{m}}\). In this case, \(S_{\textsf{m}}\) cannot produce a clause over shared variables to justify \(\overline{j}\), so conflict analysis fails with \( refineLit = j\). This causes specSMS to exit speculation mode and move to state \(\langle P, D^0 \rangle \) and \(S_{\textsf{m}}\) must decide variable j before speculating again. In this case either decision on j results in \(\langle \varPhi _{\textsf{s}}, \varPhi _{\textsf{m}}\rangle \) being sat.    \(\square \)

In addition to refining when conflict analysis fails, specSMS also has the ability to refine non-deterministically. That is, at any point during speculation, \(S_{\textsf{s}} \) can decide to stop speculation, back-jump to the decision level from which it started speculation, and choose any interface literal as \( refineLit \).

Case (2) is similar to what happens in SMS when a conflict is detected in \(S_{\textsf{s}} \). The reason for the conflict is below level i which is below the level of any decision of \(S_{\textsf{s}} \). Since decision levels below i are treated as assumptions in \(S_{\textsf{s}} \), calling \(\textsf {AnalyzeFinal} \) in \(S_{\textsf{s}} \) returns an interface clause c that blocks the current assignment in \(S_{\textsf{m}} \). The clause c is added to \(S_{\textsf{m}} \). The solvers back-jump to the smallest decision level j that makes c an asserting clause in \(S_{\textsf{m}} \). Finally, specSMS moves to \(\langle P, D^0 \rangle \).

Validation Case (3), like Case (1), is unique to specSMS. While all clauses of \(S_{\textsf{s}} \) are satisfied, the current assignment might not satisfy all clauses of \(S_{\textsf{m}} \). Thus, specSMS enters validation by switching to the configuration \(\langle F, D^M \rangle \), where M is the current decision level. Thus, \(S_{\textsf{m}} \) becomes active and starts deciding and propagating. This continues, until one of two things happen: (3a) \(S_{\textsf{m}} \) extends the assignment to satisfy all of its clauses, or (3b) a conflict that cannot be resolved with inter-modular conflict analysis is found. In the case (3a), specSMS transitions to \(\langle F, F \rangle \) and declares that \(\langle \varPhi _{\textsf{m}}, \varPhi _{\textsf{s}} \rangle \) is sat. The case (3b) is handled exactly the same as Case (1) – the literal on the trail without a reason is stored in \( refineLit \), specSMS moves to \(\langle P, D^0 \rangle \), backjumps to the level in which speculation was started, and \(S_{\textsf{m}} \) is forced to decide on \( refineLit \).

figure d

Theorem 1

specSMS terminates. If it reaches the state \(\langle F,F \rangle \), then \(\varPhi _\textsf{s} \wedge \varPhi _\textsf{m} \) is satisfiable and the join of the trails of \(\langle S_{\textsf{s}}, S_{\textsf{m}} \rangle \) is a satisfying assignment. If it reaches the state \( unsat \), \(\varPhi _\textsf{s} \wedge \varPhi _\textsf{m} \) is unsatisfiable.

4 Validation and interpolation

In this section, we augment specSMS with an interpolation procedure. To this end, we first introduce modular DRUP proofs, which are generated from specSMS in a natural way. We then present an algorithm for extracting an interpolant from a modular trimmed DRUP proof in the spirit of [11].

4.1 DRUP proofs for modular SAT

Modular DRUP proofs – a form of clausal proofs [9] – extend (monolithic) DRUP proofs [12]. A DRUP proof [12] is a sequences of steps, where each step either asserts a clause, deletes a clause, or adds a new Reverse Unit Propagation (RUP) clause. Given a set of clauses \(\varGamma \), a clause \( cls \) is an RUP for \(\varGamma \), written \(\varGamma \vdash _{ UP } cls \), if \( cls \) follows from \(\varGamma \) by unit propagation [8]. For a DRUP proof \(\pi \), let denote all clauses of the asserted commands in \(\pi \), then \(\pi \) shows that all RUP clauses of \(\pi \) follow from . If \(\pi \) contains a \(\bot \) clause, then \(\pi \) certifies is unsat.

A Modular DRUP proof is a sequence of clause addition and deletion steps, annotated with indices \( idx \) (m or s). Intuitively, steps with the same index must be validated together (within the same module \( idx \)), and steps with different indices may be checked independently. The steps are:

  1. 1.

    \((\text {asserted}, idx , cls )\) denotes that \( cls \) is asserted in \( idx \),

  2. 2.

    \((\text {rup}, idx , cls )\) denotes adding RUP clause \( cls \) to \( idx \),

  3. 3.

    \((\text {cp}( src ), dst , cls )\) denotes copying a clause \( cls \) from \( src \) to \( dst \), and

  4. 4.

    \((\text {del}, idx , cls )\) denotes removing clause \( cls \) from \( idx \).

Fig. 2.
figure 2

An example of a modular DRUP proof. Clauses are written in human-readable form as implications, instead of in the DIMACS format.

We denote the prefix of length k of a sequence of steps \(\pi \) by \(\pi ^k\). Given a sequence of steps \(\pi \) and a formula index \( idx \), we use \( act\_clauses (\pi , idx )\) to denote the set of active clauses with index \( idx \). Formally,

figure h

A sequence of steps \(\pi = c_1,\ldots , c_n\) is a valid modular DRUP proof iff for each \(c_i \in \pi \):

  1. 1.

    if \(c_i = (\text {rup}, idx , cls )\) then \( act\_clauses (\pi ^i, idx ) \vdash _{ UP } cls \),

  2. 2.

    if \(c_i = (\text {cp} ( idx ), \_, cls )\) then \( act\_clauses (\pi ^i, idx ) \vdash _{ UP } cls \), and

  3. 3.

    \(c_{|\pi |}\) is either \((\text {rup}, \textsf{m}, \bot )\) or \((\text {cp} (\textsf{s}), \textsf{m}, \bot )\).

Let be the set of all asserted clauses in \(\pi \) with index idx.

Theorem 2

If \(\pi \) is a valid modular DRUP proof, then is unsatisfiable.

Modular DRUP proofs may be validated with either one or two solvers. To validate with one solver we convert the modular proof into a monolithic one (i.e., where the steps are \(\text {asserted}\), \(\text {rup} \), and \(\text {del} \)). Let modDRUP2DRUP be a procedure that given a modular DRUP proof \(\pi \), returns a DRUP proof \(\pi '\) that is obtained from \(\pi \) by (a) removing \( idx \) from all the steps; (b) removing all \(\text {cp} \) steps; (c) removing all \(\text {del} \) steps. Note that \(\text {del} \) steps are removed for simplicity, otherwise it is necessary to account for deletion of copied and non-copied clauses separately.

Lemma 1

If \(\pi \) is a valid modular DRUP proof then is a valid DRUP proof.

Modular validation is done with two monolithic solvers working in lock step: \((\text {asserted}, cls , idx )\) steps are added to the \( idx \) solver; \((\text {rup}, idx , cls )\) steps are validated locally in solver \( idx \) using all active clauses (asserted, copied, and \(\text {rup}\)); and for \((\text {cp} ( src ), dst , cls )\) steps, \( cls \) is added to \( dst \) but not validated in it, and \( cls \) is checked to exist in the \( src \) solver.

From now on, we consider only valid proofs. We say that a (valid) modular DRUP proof \(\pi \) is a proof of unsatisfiability of \(\varPhi _{\textsf{s}} \wedge \varPhi _{\textsf{m}}\) if and (inclusion here refers to the sets of clauses).

specSMS produces modular DRUP proofs by logging the clauses that are learnt, deleted, and copied between solvers. Note that in SMS clauses may only be copied from \(S_{\textsf{s}} \) to \(S_{\textsf{m}} \), but in specSMS they might be copied in both directions.

Theorem 3

Let \(\varPhi _{\textsf{s}}\) and \(\varPhi _{\textsf{m}}\) be two Boolean formulas s.t. \(\varPhi _{\textsf{s}} \wedge \varPhi _{\textsf{m}} \models \bot \). specSMS produces a valid modular DRUP proof for unsatisfiability of \(\varPhi _{\textsf{s}} \wedge \varPhi _{\textsf{m}}\).

figure o

Trimming modular DRUP proofs. A step in a modular DRUP proof \(\pi \) is core if removing it invalidates \(\pi \). Under this definition, \(\text {del} \) steps are never core since removing them does not affect validation. Alg. 2 shows an algorithm to trim modular DRUP proofs based on backward validation. The input are two modular solvers \(S_\textsf{m} \) and \(S_\textsf{s} \) in a final conflicting state, and a valid modular DRUP proof \(\pi = c_1,\ldots ,c_n\). The output is a trimmed proof \(\pi '\) s.t. all steps of \(\pi '\) are core.

We assume that the reader is familiar with MiniSAT [6] and use the following solver methods: Propagate, exhaustively applies unit propagation (UP) rule by resolving all unit clauses; ConflictAnalysis analyzes the most recent conflict and marks which clauses are involved in the conflict; IsOnTrail checks whether a clause is an antecedent of a literal on the trail; Enqueue enqueues one or more literals on the trail; IsDeleted, Delete, Revive check whether a clause is deleted, delete a clause, and add a previously deleted clause, respectively; SaveTrail, RestoreTrail save and restore the state of the trail.

Alg. 2 processes the steps of the proof backwards, rolling back the states of the solvers. \(M_ idx \) marks which clauses were relevant to derive clauses in the current suffix of the proof. While the proof is constructed through inter-modular reasoning, the trimming algorithm processes each of the steps in the proof completely locally. During the backward construction of the trimmed proof, steps that include unmarked clauses are ignored (and, in particular, not added to the proof). For each (relevant) \(\text {rup}\) step, function chk_rup, using ConflictAnalysis, adds clauses to M. \(\text {del}\) steps are never added to the trimmed proof, but the clause is revived from the solver. For \(\text {cp}\) steps, if the clause was marked, it is marked as used for the solver it was copied from and the step is added to the proof. Finally, \(\text {asserted}\) clauses that were marked are added to the trimmed proof. Note that, as in [11], proofs may be trimmed in different ways, depending on the strategy for ConflictAnalysis.

The following theorem states that trimming preserves validity of the proof:

Theorem 4

Let \(\varPhi _{\textsf{s}}\) and \(\varPhi _{\textsf{m}}\) be two formulas such that \(\varPhi _{\textsf{s}} \wedge \varPhi _{\textsf{m}}\models \bot \). If \(\pi \) is a modular DRUP proof produced by solvers \(S_\textsf{s} \) and \(\varPhi _{\textsf{m}}\) for \(\varPhi _{\textsf{s}} \wedge \varPhi _{\textsf{m}}\), then a trimmed proof \(\pi '\) by Alg. 2 is also a valid modular DRUP proof for \(\varPhi _{\textsf{s}} \wedge \varPhi _{\textsf{m}}\).

Fig. 2 shows a trimmed proof after specSMS is executed on \(\langle \psi _0,\psi _1\rangle \) such that \(\psi _0 \triangleq ((s_1 \wedge la _1)\Rightarrow s_2)) \wedge ((s_1 \wedge \lnot la _1)\Rightarrow s_2) \wedge ((s_3 \wedge la _2)\Rightarrow s_4) \wedge ((s_3 \wedge \lnot la _2)\Rightarrow s_4)\) and \(\psi _1 \triangleq (\lnot s_1 \Rightarrow lb _1) \wedge (\lnot s_1 \Rightarrow \lnot lb _1) \wedge ((s_2 \wedge lb _2)\Rightarrow s_3) \wedge ((s_2 \wedge \lnot lb _2)\Rightarrow s_3) \wedge (s_4 \Rightarrow lb _3) \wedge (s_4 \Rightarrow \lnot lb _3))\).

4.2 Interpolation

Given a modular DRUP proof \(\pi \) of unsatisfiability of \(\varPhi _{\textsf{s}} \wedge \varPhi _{\textsf{m}}\), we give an algorithm to compute an interpolant of \(\varPhi _{\textsf{s}} \wedge \varPhi _{\textsf{m}}\). For simplicity of the presentation, we assume that \(\pi \) has no deletion steps; this is the case in trimmed proofs, but we can also adapt the interpolation algorithm to handle deletions by keeping track of active clauses.

Our interpolation algorithm relies only on the clauses copied between the modules. Notice that whenever a clause is copied from module i to module j, it is implied by all the clauses in \(\varPhi _{i}\) together with all the clauses that have been copied from module j. We refer to clauses copied from \(S_{\textsf{m}} \) to \(S_{\textsf{s}} \) as backward clauses and clauses copied from \(S_{\textsf{s}} \) to \(S_{\textsf{m}} \) as forward clauses. The conjunction of forward clauses is unsatisfiable with \(S_{\textsf{m}} \). This is because, in the last step of \(\pi \), \(\bot \) is added to \(S_{\textsf{m}} \), either through \(\text {rup}\) or by \(\text {cp}\) \(\bot \) from \(S_{\textsf{s}} \). Since all the clauses in module m are implied by \(\varPhi _{\textsf{m}}\) together with forward clauses, this means that the conjunction of forward clauses is unsatisfiable with \(\varPhi _{\textsf{m}}\). In addition, all forward clauses were learned in module s, with support from backward clauses. This means that every forward clause is implied by \(\varPhi _{\textsf{s}}\) together with the subset of the backward clauses used to derive it. Intuitively, we should therefore be able to learn an interpolant with the structure: backward clauses imply forward clauses.

Alg. 3 describes our interpolation algorithm. It traverses a modular DRUP proof forward. For each clause \( cls \) learned in module s, the algorithm collects the set of backward clauses used to learn \( cls \). This is stored in the \(\sup \) datastucture — a mapping from clauses to sets of clauses. Finally, when a forward clause c is copied, it adds \(\sup (c)\Rightarrow c\) to the interpolant.

Example 4

We illustrate our algorithm using the modular DRUP proof from Fig. 2. On the first \(\text {cp} \) step (\(\text {cp} (\textsf{m}), \textsf{s}, s_2\Rightarrow s_3\)), the algorithm assigns the \(\sup \) for clause \(s_2\Rightarrow s_3\) as itself (line 8). The first clause learnt in module s, (\(\text {rup},\textsf{s},s_3\Rightarrow s_4\)), is derived from just the clauses in module s and no backward clauses. Therefore, after RUP, our algorithm sets \(\sup (s_3\Rightarrow s_4)\) to \(\top \) (line 12). The second clause learnt in module s, \(s_1 \Rightarrow s_4\), is derived from module s with the support of the backward clause \(s_2\Rightarrow s_3\). Therefore, \(\sup (s_1\Rightarrow s_4) = \{s_2\Rightarrow s_3\}\). When this clause is copied forward to module 1, the algorithm updates the interpolant to be \((s_2\Rightarrow s_3) \Rightarrow (s_1\Rightarrow s_4)\).    \(\square \)

Next, we formalize the correctness of the algorithm. Let \(L_B(\pi ) = \{ cls \mid (\text {cp} (\textsf{m}), \textsf{s}, cls ) \in \pi \}\) be the set of clauses copied from module m to s and \(L_F(\pi ) = \{ cls \mid (\text {cp} (\textsf{s}), \textsf{m}, cls ) \in \pi \}\) be clauses copied from module s to m. From the validity of modular DRUP proofs, we have that:

Lemma 2

For any step \(c_i = (\text {cp} (\textsf{s}), \textsf{m}, cls ) \in \pi \), \((L_B(\pi ^i) \wedge \varPhi _{\textsf{s}}) \Rightarrow cls \) and for any step \(c_j = (\text {cp} (\textsf{m}), \textsf{s}, cls ) \in \pi \), \((L_F(\pi ^j) \wedge \varPhi _{\textsf{m}}) \Rightarrow cls \).

For any clause \( cls \) copied from one module to the other, we use the shorthand \(\sharp ( cls )\) to refer to the position of the copy command in the proof \(\pi \). That is, \(\sharp ( cls )\) is the smallest k such that \(c_k = (\text {cp} (i), j , cls ) \in \pi \). The following is an invariant in a valid modular DRUP proof:

Lemma 3

$$ \forall cls \in L_F(\pi ) \cdot (\varPhi _{\textsf{m}} \wedge (L_F(\pi ^{\sharp ( cls )})) \Rightarrow L_B(\pi ^{\sharp ( cls )})) $$

These properties ensure that adding \(L_B(\pi ^{\sharp ( cls )}) \Rightarrow cls \) for every forward clause \( cls \) results in an interpolant. Alg. 3 adds \((\sup ( cls ) \Rightarrow cls )\) as an optimization. Correctness is preserved since \(\sup ( cls )\) is a subset of \(L_B(\pi ^{\sharp ( cls )})\) that together with \(\varPhi _{\textsf{s}}\) suffices to derive \( cls \) (formally, \(\sup ( cls ) \wedge \varPhi _{\textsf{s}} \vdash _{ UP } cls \)).

Theorem 5

Given a modular DRUP proof \(\pi \) for \(\varPhi _{\textsf{s}} \wedge \varPhi _{\textsf{m}}\), \( itp \triangleq \{\sup (c)\Rightarrow c \mid c \in L_F(\pi )\}\) is an interpolant for \(\langle \varPhi _{\textsf{s}}\), \(\varPhi _{\textsf{m}}\rangle \).

Proof

Since all copy steps are over interface variables, the interpolant is also over interface variables. By Lemma 2 (and the soundness of \(\sup \) optimization), \(\varPhi _{\textsf{s}} \Rightarrow itp \). Next, we prove that \((\varPhi _{\textsf{m}} \wedge itp ) \Rightarrow \bot \). From Lemma 3, we have that for all \(c \in L_F(\pi )\), \((\varPhi _{\textsf{m}} \wedge L_F(\pi ^{\sharp (c)}))\Rightarrow \sup (c)\). Therefore, \((\varPhi _{\textsf{m}} \wedge L_F(\pi ^{\sharp (c)}) \wedge (\sup (c)\Rightarrow c)) \Rightarrow c\)

It is much simpler to extract interpolants from modular DRUP proofs then from arbitrary DRUP proofs. This is not surprising since the interpolants capture exactly the information that is exchanged between solvers. The interpolants are not in CNF, but can be converted to CNF after extraction.

5 Heuristics for guiding specSMS

Theoretically, speculation makes specSMS more powerful than SMS and IC3/PDR. However, in practice, deciding when to enter speculation has a major impact on the performance of specSMS. If the speculation is too greedy, specSMS performs poorly on examples where the main module is easy to solve. Similarly, if the speculation is too lazy, specSMS performs poorly on problems in which any solution to the secondary module makes the main module easy to solve. We illustrate this trade-off using an example.

Example 5

Consider a modular query: \(\langle \gamma _{ in }(\ell ,x, in ), \gamma _{{\text {SHA-1}}}( in ,x, out )\rangle \), where x is an 512-bit vector, \(\ell \) is a 160-bit vector, \( chks _i\) are 512-bit vector, and the remaining variables are the same as in \(\psi _ in \) and \(\psi _{{\text {SHA-1}}}\), and

figure p

This is an example where bi-directional search is necessary to efficiently solve the query. If deciding only on \(\gamma _{{\text {SHA-1}}}\), we encounter the hard problem of inverting \({\text {SHA-1}}_{ circ }\), if deciding in \(\gamma _{ in }\), we encounter the same problem, since an assignment for x needs to be found, based on the four values for \(\ell \). Therefore, neither immediate nor late speculation makes specSMS efficient on the problem. The ideal strategy here is to speculate after an assignment to x, to simplify \(\gamma _{ in }\).    \(\square \)

Ideally, we would like to speculate when the current modular query is too hard for the solver. As a proxy for hardness, we measure the number of conflicts the SAT solver hits. We first speculate when the main solver hits a predetermined number of conflicts. We then exponentially widen the number of conflicts between speculations. Exiting from speculation is just as important as entering speculation: the secondary solver might also get stuck in solving its module. Therefore, we use the same heuristic in the secondary solver to exit speculation.

While this is a simple heuristic, we found it to be useful in our benchmarks. The best strategy for speculation is problem-dependent. We leave development of a robust heuristic for future work.

6 Implementation and Validation

Table 1. Solving time with a timeout of 600s.

We implemented specSMS (and SMS) inside the extensible SAT-solver of Z3 [5]Footnote 2. For SMS, we simply disable speculation (Table 1).

We have validated specSMS on a set of handcrafted benchmarks, based on Ex. 1. Each benchmark is of the form \(\langle \psi _{ in }(\ell , in ), \psi _{{\text {SHA-1}}}( in , out )\rangle \), where \(\ell \) is a 2-bit vector, \( in \) is a 512-bit vector (shared), \( out \) is 160-bit vector. \(\psi _{ in }\) encodes that there are four possible messages:

figure q

and \(\psi _{{\text {SHA-1}}}( in , out )\) encodes the \({\text {SHA-1}}\) circuit together with some hash:

$$ \psi _{{\text {SHA-1}}} \triangleq ({\text {SHA-1}}_{circ}(in) \wedge out = shaVal ) $$

In the first set of experiments, we check sat queries by generating one \( msg _i\) in \(\psi _{ in }\) that produces \( shaVal \). In the second set, we check unsat queries, by ensuring that no \( msg _i\) produces \( shaVal \). To evaluate performance, we make \(\psi _{{\text {SHA-1}}}\) harder to solve by increasing the number of rounds of \({\text {SHA-1}}\) circuit encoded in the \({\text {SHA-1}}_{circ}\) clauses. We used SAT-encoding [13]Footnote 3 to generate the \({\text {SHA-1}}_{circ}\) with the different number of rounds (SAT-encoding supports 16 to 40 rounds).

We use the heuristic described in Sec. 5 to decide when to enter and exit speculation. Thus, specSMS switches modules when it hits too many conflicts in the module. In contrast, SMS only switches to the secondary solver after finding a full satisfying assignment in the main solver.

Results for each set of the queries are shown in Tab. 1. Column “# rounds” shows the number of \({\text {SHA-1}}\) rounds encoded in \(\psi _{{\text {SHA-1}}}\). The problems quickly become too hard for SMS. At the same time, specSMS solves all the queries quickly. Furthermore, the run-time of specSMS appears to grow linearly with the number of rounds.

The experiments validate our claim that switching between modules is quite effective in solving the problem. As expected, SMS gets stuck in inverting the \({\text {SHA-1}}\) function. It cannot make progress without using information from the secondary module. In contrast, specSMS switches to the secondary module once it finds that solving \({\text {SHA-1}}_{circ}(in)\) is hard. Note that, in this problem, the ideal strategy is to speculate eagerly and then branch on all the \(\ell \) variables. However, specSMS spend some time solving \({\text {SHA-1}}_{circ}(in)\). It only switches to the secondary module when it hits many conflicts in \({\text {SHA-1}}_{circ}(in)\).

7 Conclusion and Future Work

Modular SAT-solving is crucial for efficient SAT-based unbounded Model Checking. Existing techniques, embedded in IC3/PDR  [3] and extended in SMS  [1], trade the efficiency of the solver for the simplicity of conflict resolution. In this paper, we propose a new modular SAT-solver, called specSMS, that extends SMS with truly bi-directional reasoning. We show that it is provably better than SMS (and, therefore, IC3/PDR). We implement specSMS in Z3 [5], extend it with DRUP-style [12] proofs, and proof-based interpolation. This work is an avenue to future efficient SAT- and SMT-based Model Checking algorithms.

In this paper, we rely on a simple heuristic to guide specSMS when to start speculation and exit speculation. This is sufficient to show the power of bi-directional reasoning over uni-directional reasoning on our benchmarks. However, other application domains might need more complicated heuristics to make this decision. In the future, we plan to explore guiding speculation using similar strategy used for guiding restarts in a modern CDCL SAT-solver[2].

A much earlier version of speculation, called weak abstraction, is implemented in the Spacer Constrained Horn Clause (CHC) solver [10]. Since Spacer extends IC3/PDR to SMT, the choice of speculation is based on theory reasoning. Speculation starts when the main solver is satisfied modulo some theories (e.g., Linear Real Arithmetic or Weak Theory of Arrays). Speculation often prevents Spacer from being stuck in any one SMT query. However, Spacer has no inter-modular propagation and no refinement. If validation fails, speculation is simply disabled and the query is tried again without it. We hope that extending specSMS to theories will make Spacer heuristics much more flexible and effective.

DPLL(T)-style [7] SMT-solvers can be seen as modular SAT-solvers where the main module is a SAT solver and the secondary solver is a theory solver (often EUF-solver that is connected to other theory solvers such as a LIA solver). This observation credited as an intuition for SMS  [1]. In modern SMT-solvers, all decisions are made by the SAT-solver. For example, if a LIA solver wants to split on a bound of a variable x, it first adds a clause \((x \le (b-1) \vee x \ge b)\), where b is the desired bound, to the SAT-solver and then lets the SAT-solver branch on the clause. specSMS extends this interaction by allowing the secondary solver (i.e., the theory solver) to branch without going back to the main solver. Control is returned to the main solver only if such decisions tangle local decisions of the two solvers. We hope that the core ideas of specSMS can be lifted to SMT and allow more flexibility in the interaction between the DPLL-core and theory solvers.