Decidable Verification of Uninterpreted Programs
We study the problem of completely automatically verifying uninterpreted programs—programs that work over arbitrary data models that provide an interpretation for the constants, functions and relations the program uses. The verification problem asks whether a given program satisfies a postcondition written using quantifier-free formulas with equality on the final state, with no loop invariants, contracts, etc. being provided. We show that this problem is undecidable in general. The main contribution of this paper is a subclass of programs, called coherent programs that admits decidable verification, and can be decided in Pspace. We then extend this class of programs to classes of programs that are -coherent, where , obtained by (automatically) adding ghost variables and assignments that make them coherent. We also extend the decidability result to programs with recursive function calls and prove several undecidability results that show why our restrictions to obtain decidability seem necessary.
Completely automatic verification of programs is almost always undecidable. The class of sequential programs, with and without recursive functions, admits a decidable verification problem when the state-space of variables/configurations is finite, and this has been the cornerstone on which several fully automated verification techniques have been based, including predicate abstraction, and model-checking. However, when variables range over infinite domains, verification almost inevitably is undecidable. For example, even for programs manipulating natural numbers with increment and decrement operators, and checks for equality, program verification is undecidable.
In this paper, we investigate classes of programs over uninterpreted functions and relations over infinite domains that admit, surprisingly, a decidable verification problem (with no user help whatsoever, not even in terms of inductive loop invariants or pre/post conditions).
A program can be viewed as working over a data-domain that consists of constants, functions and relations. For example, a program manipulating integers works on a data-model that provides constants like , functions like , and relations like , where there is an implicit assumption on the meaning of these constants, functions, and relations. Programs over uninterpreted data models work over arbitrary data-models, where the interpretation of functions and relations are not restricted in any way, except of course that equality is a congruence with respect to their interpretations (e.g., if , then , no matter what the interpretation of is). A program satisfies its assertions over uninterpreted data models if it satisfies the assertions when working over all data-models.
The theory of uninterpreted functions is a theory that only has the congruence axioms, and is an important theory both from a theoretical and practical standpoint. Classical logic such as Gödel’s (weak) completeness theorem are formulated for such theories. And in verification, when inductive loop invariants are given, verification conditions are often formulated as formulas in SMT theories, where the theory of uninterpreted functions is an important theory used to model memory, pointers in heaps, arrays, and mathematical specifications. In particular, the quantifier-free logic of uninterpreted functions is decidable and amenable to Nelson-Oppen combination with other theories, making it a prime theory in SMT solvers.
We show, perhaps unsurprisingly, that verification of uninterpreted programs is undecidable. The main contribution of this paper is to identify a class of programs, called coherent programs, for which verification is decidable.
Program executions can be viewed abstractly as computing terms conditioned on assumptions over terms. Assignments apply functions of the underlying data-domain and hence the value of a variable at any point in an execution can be seen as computing a term in the underlying data-model. Conditional checks executed by the program can be seen as assumptions the program makes regarding the relations that hold between terms in the data-model. For example, after an execution of the statements , the program variable corresponds to the term , and the execution makes the assumption that , where is the value of variable at the start of the execution. A coherent program has only executions where the following two properties hold. The first is the memoizing property that says that when a term is recomputed by the execution, then some variable of the program already has the same term (or perhaps, a different term that is equivalent to it, modulo the assumptions seen so far in the execution). The second property, called early assumes says, intuitively, that when an assumption of equality between variables and is made by a program, superterms of the terms stored in variables and computed by the program must be stored in one of the current variables.
We show that the notion of coherence effectively skirts undecidability of program verification. Both notions of memoizing and early-assumes require variables to store certain computed terms in the current set of variables. This notion in fact is closely related to bounded path-width of the computational graph of terms computed by the program; bounded path-width and bounded tree-width are graph-theoretic properties exploited by many decision procedures of graphs for decidability of MSO and for efficient algorithms (Courcelle and Engelfriet, 2012; Seese, 1991), and there have been several recent works where they have been useful in finding decidable problems in verification (Madhusudan and Parlato, 2011; Chatterjee et al., 2016, 2015).
Our decidability procedure is automata-theoretic. We show that coherent programs generate regular sets of coherent executions, and we show how to construct automata that check whether an execution satisfies a post-condition assertion written in quantifier-free theory of equality. The automaton works by computing the congruence closure of terms defined by equality assumptions in the execution, checking that the disequality assumptions are met, while maintaining this information only on the bounded window of terms corresponding to the current valuation of variables of the program. In fact, the automaton can be viewed as a streaming congruence closure algorithm that computes the congruence closure on the moving window of terms computed by the program. The assumption of coherence is what allows us to build such a streaming algorithm. We show that if either the memoizing assumption or the early-assumes assumption is relaxed, the verification problem becomes undecidable, arguing for the necessity of these assumptions.
The second contribution of this paper is a decidability result that extends the first result to a larger class of programs — those uninterpreted programs that can be converted to coherent ones. A program may not be coherent because of an execution that either recomputes a term when no variable holds that term, or makes an assumption on a term whose superterm has been previously computed but later over-written. However, if the program was given access to more variables, it could keep the required term in an auxiliary variable to meet the coherence requirement. We define a natural notion of -coherent executions — executions that can be made coherent by adding ghost variables that are write-only and assigned at appropriate times. We show that programs that generate -coherent executions also admit a decidable verification problem. The notion of -coherence is again related to path-width — instead of demanding that executions have path-width , we allow them to have path-width , where is the set of program variables.
We also show that -coherence is a decidable property. Given a program and , we can decide if its executions can be made -coherent. Notice that when , -coherence is simply coherence, and so these results imply the decidability of coherence as well. And if they can, we can automatically build a regular collection of coherent executions that automatically add the ghost variable assignments. This result enables us to verify programs by simply providing a program and a budget (perhaps iteratively increased), and automatically check whether the program’s executions can be made -coherent, and if so, perform automatic verification for it.
The third contribution of this paper is an extension of the above results to programs with recursive function calls. We show that we can build visibly pushdown automata (VPA) that read coherent executions of programs with function calls, and compute congruence closure effectively. Intersecting such a VPA with the VPA accepting the program executions and checking emptiness of the resulting VPA gives the decidability result. We also provide the extension of verification to -coherent recursive programs.
To the best of our knowledge, the results here present the first interesting class of sequential programs over infinite data-domains for which verification is decidable111There are some automata-theoretic results that can be interpreted as decidability results for sequential programs; but these are typically programs reading streaming data or programs that allow very restricted ways of manipulating counters, which are not natural classes for software verification. See Section 7 for a detailed discussion..
The main contributions of this paper are:
We show verification of uninterpreted programs (without function calls) is undecidable.
We introduce a notion of coherent programs and show verification of coherent uninterpreted programs (without function calls) is decidable and is Pspace-complete.
We introduce a notion of -coherent programs, for any . We show that given a program (without function calls) and a constant , we can decide if it is -coherent; and if it is, decide verification for it.
We prove the above results for programs with (recursive) function calls, showing decidability and Exptime-completeness.
The paper is structured as follows. Section 2 introduces uninterpreted programs and their verification problem, and summarizes the main results of the paper. Section 3 contains our main technical result and is devoted to coherent programs and the decision procedure for verifying them, as well as the decision procedure for recognizing coherent programs. In Section 4, we show our undecidability results for general progams as well as programs that satisfy only one of the conditions of the coherence definition. Section 5 consists of our decidability results for -coherent programs. In Section 6 we extend our results to recursive programs. Related work discussion can be found in Section 7 and concluding remarks in Section 8 where we also discuss possible extensions and applications of our results.
2. The Verification Problem and Summary of Results
In this paper we investigate the verification problem for imperative programs, where expressions in assignments and conditions involve uninterpreted functions and relations. We, therefore, begin by defining the syntax and semantics of the class of programs we study, and then conclude the section by giving an overview of our main results; the details of these results will be presented in subsequent sections.
Let us begin by recalling some classical definitions about first order structures. A (finite) first order signature is a tuple , where , , and are finite sets of constants, function symbols, and relation symbols, respectively. Each function symbol and relation symbol is implicitly associated with an arity in . A first order signature is algebraic if there are no relation symbols, i.e., . We will denote an algebraic signature as instead of . An algebra or data model for an algebraic signature , is which consists of a universe and an interpretation for each constant and function symbol in the signature. The set of (ground) terms are those that can be built using the constants in and the function symbols in ; inductively, it is the set containing , and if is an -ary function symbol, and are terms, then is a term. We will denote the set of terms as or simply Terms, since the signature will often be clear from the context. Given a term and a data model , the interpretation of (or the value that evaluates to) in will be denoted by .
Our imperative programs will use a finite set of variables to store information during a computation. Let us fix to be this finite set of variables. These programs will use function symbols and relation symbols from a first order signature to manipulate values stored in the variables. We will assume, without loss of generality, that the first order signature has constant symbols that correspond to the initial values of each variable at the begining of the computation. More precisely, let represent the initial values for each variable of the program. The syntax of programs is given by the following grammar.
Here, , , , , and is a tuple of variables in and constants in .
The constructs above define a simple class of programs with conditionals and loops. Here, ‘’ denotes the assignment operator, ‘’ stands for sequencing of programs, skip is a “do nothing” statement, is a construct for conditional statements and while is our looping construct. We will also use the shorthand ‘’ as syntactic sugar for ‘’. The conditionals can be equality () atoms, predicates defined by relations , and boolean combinations of other conditionals. Formally, the semantics of the program depends on an underlying data model that provides a universe, and meaning for functions, relations, and constants; we will define this precisely in Section 2.3.
The conditionals in the above syntax involve Boolean combinations of equalities as well as relations over variables and constants. However, for technical simplicity and without loss of generality, we disallow relations entirely. Note that a relation of arity can be modeled by fixing a new constant and introducing a new function of arity and a variable . Then, each time we see , we add the assignment statement and replace the occurrence of by the conditional ‘’. Also, Boolean combinations of conditions can be modeled using the construct. Constant symbols used in conditionals and assignments can also be removed simply by using a variable in the program that is not modified in any way by the program. Hence we will avoid the use of constant symbols as well in the program syntax. Henceforth, without loss generality, we can assume that our first order signature is algebraic (), constant symbols do not appear in any of the program expressions, and our programs have conditionals only of the form or .
Consider the uninterpreted program in Figure 1. The program works on any first-order model that has an interpretation for the unary functions n and key, and an initial interpretation of the variables T, F, x, y and k. The program is similar to a program that searches whether a list segment from x to y contains a key k. However, in the program above, the functions n and key are uninterpreted, and we allow all possible models on which the program can work. Note that if and when the program terminates, we know that if , then there is an element reachable from x before reaching y such that key applied to that node is equal to k. Note that we are modeling T and F, which are Boolean constants, as variables in the program (assuming that they are different elements in the model).
Programs and in Figure 1 are also uninterpreted programs, and resemble programs that given a linked list segment from x to z, finds the node that is two nodes before the node z (i.e., find the node such that ).
Definition 0 (Executions).
An execution over a finite set of variables is a word over the alphabet .
We use quotes around letters for readability, and may sometimes skip them.
Definition 0 (Complete and Partial Executions of a program).
Complete executions of programs that manipulate a set of variables are executions over defined formally as follows:
Here, is a conditional of the form or , where .
The set of partial executions, denoted by , is the set of prefixes of complete executions in .
For the example program in Figure 1, the following word
is a partial execution of and the word is a complete execution.
Our notion of executions is more syntactic than semantic. In other words, we do not insist that executions are feasible over any data model. For example, the word is an execution though it is not feasible over any data model. Note also that the complete executions of a program capture (syntactically) terminating computations, i.e., computations that run through the entire program.
It is easy to see that an NFA accepting (as well as for ) of size linear in , for any program , can be easily constructed in polynomial time from , using the definitions above and a standard translation of regular expressions to NFAs.
For the program in Figure 1, its set of executions is given by the following regular expression
where is the regular expression
2.3. Semantics of Programs and The Verification Problem
Terms computed by an execution
We now define the set of terms computed by executions of a program over variables . The idea is to capture the term computed for each variable at the end of an execution. Recall that is the set of constant symbols that denote the initial values of the variables in when the execution starts, i.e., denotes the initial value of variable , etc. Recall that Terms are the set of all terms over signature . Let be the alphabet of executions.
The term assigned to a variable after some partial execution is captured using the function defined inductively as follows.
The set of terms computed by an execution is
Notice that the terms computed by an execution are independent of the assume statements in the execution and depend only on the assignment statements.
Consider the execution (from Example 4) below
For this execution, the set of terms computed can be visualized by the computation graph in Figure 2. Here, the nodes represent the various terms computed by the program, the solid directed edges represent the immediate subterm relation, the solid lines represent the assumptions of equality made in the execution on terms, and the dashed lines represent the assumptions of dis-equality made by the execution. The labels on nodes represent the variables that evaluate to the terms at the end of the execution.
Hence, we have , , , , , , , and .
Equality and Disequality Assumptions of an Execution
Though the assume statements in an execution do not influence the terms that are assigned to any variable, they play a role in defining the semantics of the program. The equalities and disequalities appearing in assume statements must hold in a given data model, for the execution to be feasible. We, therefore, identify what these are.
For an (partial) execution , let us first define the set of equality assumes that makes on terms. Formally, for any execution , the set of equality assumes defined by , called , is a subset of defined as follows.
The set of disequality assumes, , can be similarly defined inductively.
Consider the execution (from Example 4) below
We have and .
Semantics of programs
We define the semantics of a program with respect to an algebra or data model that gives interpretations to all the constants and function symbols in the signature. An execution is said to be feasible with respect to a data model if, informally, the set of assumptions it makes are true in that model. More precisely, for an execution , recall that and are the set of equality assumes and disequality assumes over terms computed in . An execution is feasible in a data-model , if for every , , and for every , .
The Verification Problem
Let us now define the logic for postconditions, which are quantifier-free formulas Boolean combination of equality constraints on variables. Given a finite set of variables , the syntax for postconditions is defined by the following logic .
where above, .
Note that a more complex post-condition in the form of a quantifier-free formulae using the functions/relations/constants of the underlying data domain and the current variables can be incorporated by inserting code at the end of the program that computes the relevant terms, leaving the actual postcondition to check only properties of equality over variables.
We can now define the verification problem for uninterpreted programs.
Definition 0 (The Verification Problem for Uninterpreted Programs).
Given a formula over a set of variables , and a program over , determine, for every data-model and every execution that is feasible in , if satisfies the formula under the interpretation that maps every variable to .∎
It is useful to observe that the verification problem for a program with postcondition in can be reduced to the verification of a program with additional assumes and statements and postcondition . Thus, without loss of generality, we may assume that the postcondition is fixed to be . Observe that in this situation, the verification problem essentially reduces to determining the existence of an execution that is feasible in some data model. If there is a feasible execution (in some data model) then the program violates its postcondition; otherwise the program is correct.
2.4. Main Results
In this paper, we investigate the decidability of the verification problem for uninterpreted programs. Our first result is that this problem is, in general, undecidable. We discuss this in detail in Section 4.
Result #1: The verification problem for uninterpreted programs is undecidable.
Since the general verification problem is undecidable, we identify a special class of programs for which the verification problem is decidable. In order to describe what these special programs are, we need to introduce a notion of coherent executions. Observe that as an execution proceeds, more (structurally) complex terms get computed and assigned to variables, and more assume statements identify constraints that narrow the collection of data models in which the execution is feasible. Coherent executions satisfy two properties. The first property, that we call memoizing, requires that if a term is computed (through an assignment) and either or something “equivalent” (w.r.t. to the equality assumes in the execution) to was computed before in the execution, then it must be currently stored in one of the variables. This is best illustrated through an example. Consider the partial execution
of program (in Figure 1). The term is re-computed in the last step, but it is currently stored in the variable g. On the other hand, a similar partial execution
of is not memoizing since when is recomputed in the last step, it is not stored in any variable; the contents of variable y, which stored when it was first computed, have been over-written at this point, or, in other words, the term was “dropped” by the execution before it was recomputed. The second property that coherent executions must satisfy is that any step of the form in the execution comes “early”. That is, any superterms of the terms stored in and computed by the execution upto this point, are still stored in the program variables and have not been overwritten. The formal definition of coherent executions will be presented later in Section 3 . Finally, a program is coherent if all its executions are coherent. The most technically involved result of this paper is that the verification problem for coherent uninterpreted programs is decidable.
Result #2: The verification problem for coherent uninterpreted programs is decidable.
The notion of coherence is inspired by the notion of bounded pathwidth, but is admittedly technical. However, we show that determining if a given program is coherent is decidable; hence users of the verification result need not ensure that the program are coherent manually.
Result #3: Given a program, the problem of checking whether it is coherent is decidable.
The notion of coherence has two properties, namely, that executions are memoizing and have early-assumes. Both these properties seem to be important for our decidability result. The verification problem for programs all of whose executions satisfy only one of these two conditions turns out to be undecidable.
Result #4: The verification problem for uninterpreted programs whose executions are memoizing is undecidable. The verification problem for uninterpreted programs whose executions have early assumes is undecidable.
The memoizing and early-assume requirements of coherence may not be satisfied by even simple programs. For example, program in Figure 1 does not satisfy the memoizing requirement as demonstrated by the partial execution above. However, many of these programs can be made coherent by adding a finite number of ghost variables. These ghost variables are only written and never read, and therefore, play no role in the actual computation. They merely remember terms that have been previously computed and can help meet the memoizing and early-assume requirements. We show that given a budget of variables, we can automatically check whether a corresponding coherent program with additional ghost variables exists, and in fact compute a regular automaton for its executions, and verify the resulting coherent program. The notation and terminology for -coherent programs is more complex and we delay defining them formally to Section 5, where they are considered.
Result #5: Given a program and , we can decide whether executions of can be augmented with ghost variables and assignments so that they are coherent (i.e., check whether is -coherent). Furthermore, if such a coherent program exists, we can construct it and verify it against specifications.
Finally, in Section 6, we consider programs with recursive function calls, and extend our results to them. In particular, we show the following two results.
Result #6: The verification problem for coherent uninterpreted programs with recursive function calls is decidable.
Result #7 Given a program , with recursive function calls, and , we can decide whether executions of can be augmented with local ghost variables (for each function) and interleaved ghost assignments that results in a coherent program. Furthermore, if such a coherent program exists, we can construct it and verify if against specifications.
3. Verification of Coherent Uninterpreted Programs
The verification problem for uninterpreted programs is undecidable; we will establish this result in Section 4. In this section, we establish our main technical results, where we identify a class of programs for which the verification problem is decidable. We call this class of programs coherent. We begin by formally defining this class of programs. We then present our algorithm to verify coherent programs. Finally, we conclude this section by showing that the problem of determining if a given program is coherent is also decidable.
Before presenting the main technical content of this section, let us recall that an equivalence relation is said to be a congruence if whenever , , … and is an -ary function then . Given a binary relation , the congruence closure of , denoted , is the smallest congruence containing .
For a congruence on Terms, the equivalence class of a term will be denoted by ; when , we will write this as instead of . For terms and congruence on Terms, we say that is a superterm of modulo if there are terms such that , and is a superterm of .
3.1. Coherent Programs
Coherence is a key property we exploit in our decidability results, and is inspired by the concept of bounded pathwidth. In order to define coherent programs we first need to define the notion of coherence for executions. Recall that, for a partial execution , denotes the set of equality assumes made in .
Definition 0 (Coherent executions).
We say that a (partial or complete) execution over variables is coherent if it satisfies the following two properties.
Let be a prefix of and let . If there is a term such that , then there must exist some such that .
- Early Assumes.:
Let be a prefix of and let and . If there is a term such that is either a superterm of or of modulo , then there must exist a variable such that .
Formally, the memoizing property says that whenever a term is recomputed (modulo the congruence enforced by the equality assumptions until then), there must be a variable that currently corresponds to . In the above definition, the assignment is computing a term , and if has already been computed (there is a term computed by the prefix that is equivalent to ), then we demand that there is a variable which after , holds a term that is equivalent to .
The second requirement of early assumes imposes constraints on when steps are taken within the execution. We require that such assume statements appear before the execution “drops” any computed term that is a superterm of the terms corresponding to and , i.e., before the execution reassigns the variables storing such superterms; notice that also includes those terms that have been computed along the execution and might have been dropped. Formally, we demand that whenever an assume statement is executed equating variables and , if there is a superterm () of either the term stored in or modulo the congruence so far, then there must be a variable () storing a term equivalent to .
Finally, we come to the main concept of this section, namely, that of coherent programs.
Definition 0 (Coherent programs).
A coherent program is a program all of whose executions are coherent.
Any extension of to a complete execution of , will not be coherent. This is because is not memoizing — when is recomputed in the last step, it is not stored in any variable; the contents of variable y, which stored when it was first computed, have been over-written at this point.
On the other hand, the following execution over variables
is also not coherent because is not early. Observe that , , and . Now , is a superterm of but is not stored in any variable.
Consider the programs in Figure 1. is not coherent because of partial execution above. On the other hand, program is coherent. This is because whenever an execution encounters , both d and k have no superterms computed in the execution seen so far. The same holds for the at the end of an execution due to the while loop. Further, whenever a term gets dropped, or over-written, it never gets computed again, even modulo the congruence induced by the assume equations. Similar reasoning establishes that is also coherent.
3.2. Verifying Coherent Programs
We are now ready to prove that the verification problem for coherent programs is decidable. Recall that, without loss of generality, we may assume that the postcondition is . Observe that, when the postcondition is , a program violates the postcondtion, if there is an execution and a data model such that is feasible in , i.e., every equality and disequality assumption of holds in . On the face of it, this seems to require evaluating executions in all possible data models. But in fact, one needs to consider only one class of data models. We begin by recalling the notion of an initial model.
Given a binary relation of equalities, is said to satisfy (or holds in ) if for every pair , . For a relation , there is a canonical model in which holds.
The initial term model for over an algebraic signature is where
for any , and
for any -ary function symbol and terms .
An important property of the initial term model is the following.
Let be a binary relation on terms, and be any model satisfying . For any pair of terms , if then .
Any model defines an equivalence on terms as follows: iff . Observe that is a congruence, and if satisfies , then . Thus, . Next, observe that for the term model , . The proposition follows from these observations. ∎
One consequence of the above proposition is the following. Let be a set of equalities, be terms, and be a data model satisfying . If then . This means that to check the feasibility of an execution , it suffices to check its feasibility in .
Let be any execution. There is a data model such that is feasible in if and only if is feasible in .
That is, an execution is feasible iff .
Let us return to the problem of verifying if a program satisfies the postcondition . This requires us to check that no execution of the program is feasible in any data model. Let us now focus on the simpler problem of execution verification — given an execution check if there is some data model in which is feasible. If we can solve the execution verification problem, then we could potentially solve the program verification problem; since the set of executions of a program are regular, we could run the execution verification algorithm synchronously with the NFA representing the set of all program executions to see if any of them are feasible.
Corollary 6 has an important consequence for execution verification — to check if is feasible, evaluate in the data model . If the execution verification algorithm is to be lifted to verify all executions of a program, then the algorithm must evaluate the execution as the symbols come in. It cannot assume to have the entire execution. This poses challenges that must be overcome. First the term model is typically infinite and cannot be constructed explicitly. Second, since equality assumptions come in as the execution unfolds, is not known at the beginning and therefore, neither is the exact term model on which must be evaluated known. In fact, in general, we cannot evaluate an arbitrary execution in a term model in an incremental fashion. The main result of this section shows that we can exploit properties of coherent executions to overcome these challenges.
To explain the intuition behind our algorithm, let us begin by considering a naïve algorithm that evaluates an execution in a data model. Suppose the data model is completely known. One algorithm to evaluate an execution in , would keep track of the values of each program variable with respect to model , and for each assume step, check if the equality or disequality assumption holds in . When is the term model, the value that variable takes after (partial) execution , is the equivalence class of with respect to congruence defined by all the equality assumptions in the complete execution .
Our algorithm to verify an execution, will follow the basic template of the naïve algorithm above, with important modifications. First, after a prefix of the execution , we have only seen a partial set of equality assumptions and not the entire set. Therefore, the value of variable that the algorithm tracks will be and not . Now, when a new equality assumption is seen, we will need to update the values of each variable to be that in the term model that also satisfies this new equation. This requires updating the congruence class of the terms corresponding to each variable as new equalities come in. In addition, the algorithm needs to ensure that if a previously seen disequality assumption is violated because of the new equation, it can be determined, eventhough the disequality assumption maybe between two terms that are no longer stored in any of the program variables. Second, our algorithm will also track the interpretation of the function symbols when applied to the values stored for variables in the program. Thus, after a prefix , the algorithm constructs part of the model when restricted to the variable values. This partial model helps the algorithm update the values of variables when a new equality assume is read. The third wrinkle concerns how is stored. We could store a representative term from this equivalence class. This would result in an algorithm whose memory requirements grow with the execution. Instead the algorithm only maintains, for every pair of variables , whether their values in are equal or not. This means that the memory requirements of the algorithm do not grow with the length of the execution being analyzed. Thus, we will in fact show, that the collection of all feasible partial executions is a regular language.
In order to be able to carry out the above analysis incrementally, our algorithm crucially exploits the coherence properties of the execution. To illustrate one reason why the above approach would not work for non-coherent executions, consider a prefix such that , , , and . Let us assume that . Suppose we now encounter . This means that in the term model that satisfies this equality, the values of variables and are the same. However, this is possible only if the algorithm somehow maintains the information that and are the result of hundred applications of to the values in and . This cannot be done using bounded memory. Notice, however, that in this case is not a coherent execution because the assume at the end is not early. Early assumes ensure that the effect of an new equality assumption can be fully determined on the current values to the variables.
To understand the importance of the memoizing property in the decision procedure, consider the execution . This execution trivially satisfies the “early assumes” criterion. However it is not memoizing since the terms have been re-computed after they have been dropped. Now, suppose that is a complete extension of . Notice that is not memoizing but still satisfies the “early assumes” criterion (as it has no equality assumptions). Now, in order for the algorithm to correctly determine that this execution is infeasible, it needs to correctly maintain the information that . This again, is not possible using bounded memory.
We will now flesh out the intuitions laid out above. We will introduce concepts and properties that will be used in the formal construction and its correctness proof.
Recall that our algorithm will track the values of the program variables in a term model. When we have a coherent execution , the terms corresponding to program variables obey a special relationship with the set of terms constructed anytime during the execution and with the equality assumptions seen in . We capture this through the following definition which has been motivated by the condition of early assumes.
Definition 0 (Superterm closedness modulo congruence).
Let be a subterm closed set of terms. Let be a set of equations on and be its congruence closure. Let and let . Then, is said to be closed under superterms with respect to , and if for any term such that is a superterm of either or modulo , there is term such that .
Coherent executions ensure that the set of values of variables (or equivalently the set of terms corresponding to the variables) is superterm closed modulo congruence with respect to a newly encountered equality assumption; observe that for any partial execution , is subterm closed.
Let be a coherent execution over variables and let be any prefix of . Then is closed under superterms with respect to , and .
As pointed out in the overview, our algorithm will not explicitly track the terms stored in program variable, but instead track the equivalence between these terms in the term model. In addition, it also tracks the interpretations of function symbols on the stored terms in the term model. Finally, it will store the pairs of terms (stored currently in program variables) that have been assumed to be not equal in the execution. The following definition captures when such an algorithm state is consistent with a set of terms, equalities, and disequalities. In the definition below, the reader may think of as the set of terms corresponding to each program variable, as the set equality assumptions, and as the set of disequality assumptions after a prefix of the execution.
Definition 0 (Consistency).
Let be a set of terms, a set of equations on terms, and be a set of disequalities on terms. Let be an equivalence relation on , be a symmetric relation, and be a partial interpretation of function symbols, i.e., for any -ary function symbol , is a partial function mapping -tuples in to . We will say is consistent with respect to iff the following hold.
For , if and only if , i.e., and evaluate to the same value in ,
iff there are terms such that , and and .
There are two crucial properties about a set that is superterm closed (Definition 7). When we have a state that is consistent (as per Definition 9), we can correctly update it when we add an equation by doing a “local” congruence closure of the terms in . This is the content of Lemma 10.
Let be a set of subterm-closed set of terms, be a set of equalities on , and be a set of disequalities. Let be a set closed under superterms with respect to and some pair . Let be consistent with . Define to be the smallest equivalence relation on such that
for every -ary function symbol and terms such that , , and for each , we have .
In addition, take and
Then is consistent with .
The second important property about being superterm closed is that feasibility of executions can be checked easily. Recall that, our previous observations indicate that an execution is feasible, if all the disequality assumptions hold in the term model, i.e., if is a set of equality assumptions and is a set of disequality assumptions, feasibility requires checking that . Now, we show that when is superterm closed, then checking this condition when a new equation is added to can be done by just looking at ; notice that may have disequalities involving terms that are not in , and so the observation is not trivial.
Let be a set of subterm-closed set of terms, be a set of equalities on , and be a set of disequalities such that . Let and let be such that is closed under superterms with respect to and . Let be consistent with . Then, iff there are terms such that and , where is the equivalence relation on defined in Lemma 10.
Further, the notion of consistency allows us to correctly check for feasib