The Logic Programming Paradigm

debugging [13,14]' or more traditional interactive debuggers [11,20J may be applied. Global Analysis, Partial Specifications, and Assertions 167 started, for example using abstract diagnosis [13,14], in order to detect the cause of the error. It is also possible that a (part of the) assertion cannot be proved nor disproved. In this case some assertions, or part of them, remain in check status, and possibly warning messages are presented to the user. Note that it may also be interesting to implement analysis in a demanddriven way, so that information is inferred only for the program points which include assertions. The advantage of this approach is that it may be more efficient. However, three other considerations should be weighted against this. First, for many properties it is not possible to isolate the analysis of a given program point, and a global fixpoint has to be reached in any case, which requires analyzing at least the whole module involved. Also, the results of analysis are typically useful in other stages of compilation (e.g., to perform program specialization or other optimizations). Finally, in our experience bugs can often be detected by visual inspection of the assertions containing the information inferred by the analyzer, sometimes for program points which are "distant" from the user-provided check assertions. Compile-time checking is discussed further in Section 6. 3 The Assertion Language Assertions may be used in different contexts and for different purposes. In run-time checking, assertions are traditionally used to express conditions which should hold at run-time. A usual example is to check that the value of a variable remains within a given range at a given program point. In declarative debugging [41], assertions have been used in order to replace the oracle by allowing the user expressing properties of the intended behaviour of the program [18,19,6]. Assertions can also be used to express properties about the program to be checked at compile-time. An example of this are type declarations (e.g., [30,42], functional languages, etc.), which have been shown to be useful in debugging. Assertions have also been used to provide information to an optimizer in order to perform additional optimizations during code generation (e.g., [42], which also implements checking). Assertions have also been proposed as a means of providing additional information to the analyzer, which it can use both to increase the precision of the information it infers and/or to perform additional optimizations during code generation [45,43,32,31]. Also, assertions can be used to represent analysis output in source form and for communication between different modules of the compiler which deal with analysis information [8]. The assertion language used in our framework has been designed with the aim of being useful in all the contexts mentioned above. With this objective in mind, we depart from previous proposals in allowing more general properties to be expressed. Each tool in each of the contexts will then use the properties which are relevant to it. Assertions are provided to specify the program points to which the properties are "attached." In this sense they 168 Manuel Hermenegildo, German Puebla, and Francisco Bueno calls qsort(A,B) : list(A). 1. Ai success qsort(A,B) : list(A) => list(B). 1. A2 comp qsort(A,B) : (list(A),var(B» + does_not_fail. 1. A3 qsort([XIL],R) :partition(L,X,Ll,L2), qsort(L2,R2), qsort(Ll,Rl), append(Rl,[XIR2],R). qsort ([] , [] ) . Fig. 2. An example predicate definition with assertions work as schemas. Due to space limitations, we do not present here the complete assertion language, but rather we concentrate on a subset of it which suffices for illustrating the main concepts involved in compile-time and runtime checking of assertions. In particular, we will focus on predicate assertions rather than on program point assertions. Also for brevity, we will use only operational assertions, although the assertion language also includes declarative assertions (inmodel/outmodel). A more detailed description of the assertion language can be found in [36]. Predicate assertions relate properties to the invocations of a predicate. Three kinds of predicate assertions are provided; they relate properties to the execution states at the time of calling the predicate, at the time of its success, and to the whole of its computation. More than one predicate assertion (of the same or different kinds) may be given for the same predicate. In such a case, all of them should hold and composition of predicate assertions should be interpreted as their conjunction. We first illustrate the use of this kind of assertions with an example. Figure 2 presents (part of) a CIAO [7] program which implements the quicksort algorithm for sorting lists in ascending order. The predicate qsort is annotated with predicate assertions which express properties which the user expects to hold for the program.7 Three assertions are given for predicate qsort: Ai, A2, and A3, the meaning of which is explained below. Assertions on Success States. They are similar in nature to the postconditions used in program verification. They can be expressed in our assertion language using the assertion schema' : success Pred => Postcond.' It should be interpreted as "for any call of the form Pred which succeeds, on success Postcond 7 Both for convenience, i.e., so that the assertions concerning a predicate appear near its definition in the program text, and for historical reasons, i.e., mode declarations in Prolog or entry and trust declarations in PLAI [8] we write predicate assertions as directives. Depending on the tool different choices could be implemented, including for example putting assertions in separate files or incremental addition of assertions in an interactive environment. Global Analysis, Partial Specifications, and Assertions 169 should hold." For example, we can use the following assertion in order to require that the output of the procedure qsort for sorting lists be a list: success qsort(A,B) => list(B). Note that, in contrast to other programming paradigms, in (C)LP calls to a predicate may either succeed or fail. The postcondition stated in a success assertion only refers to successful executions. Assertions Restricted to a Subset of the Calls. Sometimes we are interested in properties which refer not to all invocations of a predicate, but rather to a subset of them. With this aim we allow the addition of preconditions (Precond) to predicate assertions as follows: 'Pred : Precond.' For example, success assertions can be restricted and we obtain an assertion of the form ': success Pred : Precond => Postcond,' which should be interpreted as "for any call of the form Pred for which Precond holds, if the call succeeds then on success Postcond should also hold." Note that' : success Pred => Postcond' is equivalent to ': success Pred : true => Postcond.' For example, the assertion A2 in Figure 2 requires that if qsort is called with a list in the first argument position and the call succeeds, then on success the second argument position should also be a list. Assertions on Call States. It is also possible to use assertions to describe properties about the calls for a predicate which may appear during program execution. This is useful for at least two reasons. If we perform goal-dependent analysis, a variation of calls assertions, namely entry assertions (see [8]), may be used for improving analysis information.8 They can also be used to check whether any of the calls for the predicate is not in the expected set of calls (the "inadmissible" calls of [35]). An assertion of the kind ': calls Pred : Cond' should be interpreted as "all calls of the form Pred should satisfy Cond." An example of this kind of assertion is A1 in Figure 2 which expresses that in all calls to predicate qsort the first argument should be a list. Assertions on the Computation of Predicates. Many properties which refer to the computation of the predicate, rather than the input-output behaviour, are not expressible with the assertions presented above. In particular, no property which refers to (a sequence of) intermediate states in the computation of the predicate can be easily expressed using calls and success predicate assertions only. Examples of properties of the computation which we may be interested in are: non-failure, termination, determinacy, non-suspension, non-floundering, etc. In our language this sort of properties are expressed by an assertion of the kind ': comp Pred : Precond + Comp-prop,' which is interpreted as "for any call of the form Pred for which Precond holds, 8 The entry (and trust) declarations are also instrumental in incremental modular analysis. 170 Manuel Hermenegildo, German Puebla, and Francisco Bueno Comp-prop should also hold for the computation of Pred." Again, the field ': Precond' is optional. For example, A3 in Figure 2 requires that all calls to predicate qsort with the first argument being a list and the second a variable do not fail. 4 Defining Properties Whereas each kind of assertion indicates when, i.e., in which states or sequences of states, to check the given properties, the properties themselves define what to check. As mentioned before, properties are used to say things such as "X is a list of integers," "Y is ground," "p(X) does not fail," etc. and in our framework they are logic predicates, in the sense that the evaluation of each property either succeeds or fails. The failure or success of properties typically needs to be determined at the time when the assertions in which they appear are checked. As also mentioned previously, assertions can be checked both at compile-time and at run-time. In order to simplify the discussion, in this section we will concentrate exclusively on run-time checking (the role of properties during compile-time checking will be discussed in Section 6). In order to make it possible to check a

[1]  Dale Miller,et al.  From operational semantics to abstract machines , 1992, Mathematical Structures in Computer Science.

[2]  Saumya K. Debray,et al.  Non-Failure Analysis for Logic Programs , 1997, ICLP.

[3]  Peter D. Mosses Compiler Generation Using Denotational Semantics , 1976, MFCS.

[4]  Peter J. Stuckey,et al.  Negation and Constraint Logic Programming , 1995, Inf. Comput..

[5]  Arthur C. Fleck,et al.  Semantic Specification Using Logic Programs , 1989, NACLP.

[6]  Peter J. Stuckey,et al.  Models for Using Stochastic Constraint Solvers in Constraint Logic Programming , 1996, PLILP.

[7]  David A. Schmidt Denotational Semantics: A Methodology for Language Development by Phil , 1987 .

[8]  Yehoshua Sagiv,et al.  Automatic Termination Analysis of Logic Programs , 1997, ICLP.

[9]  Susan Stepney,et al.  High integrity compilation - a case study , 1993 .

[10]  Peter Fritzson,et al.  Generating an Efficient Compiler for a Data Parallel Language from a Denotational Specification , 1994, CC.

[11]  Konstantinos Sagonas,et al.  XSB as an efficient deductive database engine , 1994, SIGMOD '94.

[12]  Rajeev Alur,et al.  The Theory of Timed Automata , 1991, REX Workshop.

[13]  Subrata Kumar Das,et al.  Deductive Databases and Logic Programming , 1992 .

[14]  C. R. Ramakrishnan,et al.  Practical program analysis using general purpose logic programming systems—a case study , 1996, PLDI '96.

[15]  I-Peng Lin,et al.  Compiling dataflow analysis of logic programs , 1992, PLDI '92.

[16]  John Wylie Lloyd,et al.  Foundations of Logic Programming , 1987, Symbolic Computation.

[17]  Gopal Gupta,et al.  Horn Logic Denotations and Their Applications , 1999, The Logic Programming Paradigm.

[18]  David A. Schmidt On the need for a popular formal semantics , 1996, CSUR.

[19]  Serge Abiteboul,et al.  Foundations of Databases , 1994 .

[20]  Flemming Nielson,et al.  Two-Level Semantics and Code Generation , 1988, Theor. Comput. Sci..

[21]  Joxan Jaffar,et al.  Constraint logic programming , 1987, POPL '87.

[22]  David A. Schmidt,et al.  Programming language semantics , 1996, CSUR.

[23]  C. A. R. HOARE,et al.  An axiomatic basis for computer programming , 1969, CACM.

[24]  Edward P. K. Tsang,et al.  Foundations of constraint satisfaction , 1993, Computation in cognitive science.

[25]  Charles Consel,et al.  Architecture Software Using: A Methodology for Language Development , 1998, PLILP/ALP.

[26]  Mitchell Wand Semantics-directed machine architecture , 1982, POPL '82.

[27]  Kazunori Ueda,et al.  Guarded Horn Clauses , 1986, LP.

[28]  Peter Lee Realistic compiler generation , 1989, Foundations of Computing Series.