Data-Flow Analysis for Constraint Logic-Based

Languages

Roberto Bagnara

Dipartimento di Informatica Università di Pisa Corso Italia, 40 56125 Pisa

Dottorato di Ricerca in Informatica Pisa – Genova – Udine

VIII ciclo A Franco

I believe in things that are developed through hard work. I always like people who have developed long and hard, especially through introspection and a lot of dedication.

I think what they arrive at is usually a much deeper

and more beautiful thing

than the person who seems to have this ability and fluidity

from the beginning.

— BILL EVANS, from Contemporary Keyboard (1981)

Acknowledgments

Firstly I wish to thank my friend and advisor Giorgio Levi for his encouragement and guidance. Giorgio supported me in all possible ways and helped me to overcome the difficult periods.

Thanks (for various reasons) to my former colleagues at the Department of Physics of the University of Bologna: Antonio Bassi, Dante Bollini, Luigi degli Esposti, Pierluigi Frabetti, Mauro Lolli, Patrizia Nicoli, and Umberto Placci.

Thanks to all my former colleagues at CERN for their friendship and for providing an environment where I could learn so much: Tim Berners-Lee, Andre Bogaerts, Roberto Divià, Philippe Gavillet, Giorgio Heiman, Hans Müller, Chris Parkman, Antonio Pastore, Yves Perrin, Jørgen Petersen, Achille Petrilli, Louis Tremblet, Bruce Wessels, and Pietro Zanarini.

Thanks to all my colleagues and friends at the Dipartimento di Informatica of the University of Pisa. In particular, thanks to Bruno Bacci, Piero Bonatti, Alessio Bragadini, Marco Comini, Simone Contiero, Marco Danelutto, Pierpaolo Degano, Andrea Dell’Amico, Agostino Dovier, Moreno Falaschi, Paolo Ferragina, Tito Flagella, Luca Francesconi, Maurizio Gabbrielli, Roberto Giacobazzi, Francesca Levi, Paolo Mancarella, Andrea Masini, Dino Pedreschi, Susanna Pelagatti, Corrado Priami, Paola Quaglia, Alessandra Raffaetà, Francesca Rossi, Francesca Scozzari, Laura Semini, Alessandro Sperduti, Stefano Suin, Franco Turini, Paolo Volpe, and Enea Zaffanella. Special thanks are due to Enea for being a really nice person and a good friend of mine, for all the work and the discussions done together, and for helping me with some technical issues. Thanks to Maria Cristina Scudellari for the work we have done together on the material that is included in Chapter 7.

Thanks to Kim Marriott for inviting me to visit the Monash University of Melbourne. The month I have spent there was of great inspiration.

I have been fortunate to meet some exceptionally good teachers: Dino Pieri, Attilio Forino, Giorgio Levi, Fabrizio Luccio, Marco Vanneschi, and Simone Martini. To them goes my deep gratitude.

I wish to thank Maurizio Gabbrielli, Roberto Giacobazzi, Patricia M. Hill, Giorgio Levi, Catiuscia Palamidessi, Francesca Rossi, Vijay Saraswat, and Enea Zaffanella for reading draft versions of this thesis and for providing many useful comments. I am particularly grateful to the external reviewers of this thesis, Andrew M. King and William H. Winsborough, for their care and competence. Andy and Will gave me a lot of useful suggestions. Thanks also to Tony Cohn and Pat Hill for allowing me to finish the thesis with relative peace of mind.

Last but most important, I thank my parents for their support and encouragement. I am especially grateful to my brother Abramo with whom I have shared these eighteen years of computer science. In particular, the help he gave me in the development of China is invaluable.

Thanks to Rossana for being with me, for her encouragement, and for putting up with me during these last months when I was working day and night on the thesis.

Ringraziamenti^{1}

Devo molto a molte persone, come tutti. Qui desidero ringraziare tutte quelle persone che, in qualche modo, hanno contribuito a che questa tesi fosse scritta. Farò questo in modo approssimativamente cronologico.

Innanzitutto voglio ringraziare mio padre, di un’infinità di cose, naturalmente, ma in particolare per avermi trasmesso il senso della qualità. Ringrazio mia madre, per avermi sostenuto nei tanti momenti difficili.

Il mio rapporto con l’informatica è iniziato circa 18 anni fa ed è costellato di persone che mi hanno aiutato e che ricordo con affetto. Dunque si torna indietro all’ITIS di Cesena, anno 1979. Grazie a Pierluigi Mengolini che mi ha introdotto all’uso delle calcolatrici programmabili e al microprocessore Zilog Z80. Grazie a Roberto Giunchi che mi consentì di costruire il mio primo microcomputer invece di un televisore in bianco e nero come usava a quei tempi in quella scuola; grazie a mio padre che finanziò il progetto e grazie a mio fratello Abramo che partecipò con foga selvaggia.

1Questa sezione di ringraziamenti è insolitamente (per qualcuno, forse, intollerabilmente) lunga. Ho ritenuto che, dopo tanto lavoro, mi fosse consentito esprimere con una certa libertà la mia gratitudine alle persone che hanno avuto un influsso, talvolta determinante, sulla mia “storia informatica” e dunque su questa tesi. Con questa nota desidero rassicurare il lettore interessato ai soli contenuti scientifici sul fatto che egli può ignorare questa sezione senza alcun pregiudizio per la comprensione del seguito.

Fuori dalla scuola, grazie a Franco Dapporto per avermi fatto usare la sua TI58, per le innumerevoli discussioni sull’informatica, e per le ore passate a “sbilinare” con un programma per gli scacchi scritto in Fortran. Grazie a Valeriano Ballardini per tutto quello che mi ha insegnato da quando ci siamo conosciuti.

Dipartimento di fisica dell’università di Bologna, 1984–1987. Grazie a Dante Bollini per la libertà con cui mi consentì di svolgere il mio lavoro, il che mi permise di sperimentare nuove tecniche e di imparare moltissime cose oltre a quelle che mi insegnò lui stesso (ivi incluso il “bootstrap da pannello” dell’HP1000!). Grazie ai miei colleghi Antonio Bassi, Luigi degli Esposti, Mauro Lolli, Patrizia Nicoli e Umberto Placci, per tutte le discussioni istruttive che abbiamo avuto e, soprattutto, per la loro amicizia. Grazie ad Antonio per il suo entusiasmo e per la sua disponibilità. Sono particolarmente grato a Mauro per tutto quello che mi ha insegnato, per tutte le nostre discussioni sulla contraddizione tra qualità e “tempi di consegna”, e per non aver mai rinunciato al confronto. Grazie anche a Pierluigi Frabetti per il suo incoraggiamento.

CERN, laboratorio europeo per la fisica delle particelle, 1985 e 1988–1989. Grazie a Philippe Gavillet, che nel 1985 mi introdusse al problema e alle tecniche di diagnosi remota e automatica dei guasti hardware e software. Grazie ai miei colleghi Tim Berners-Lee, Andre Bogaerts, Roberto Divià, Hans Müller, Chris Parkman, Antonio Pastore, Yves Perrin, Jørgen Petersen, Louis Tremblet e Bruce Wessels per la loro amicizia e per aver costituito un gruppo di lavoro che per me è stato una vera palestra. Grazie a Louis, con il quale ho svolto la prima parte del mio lavoro al CERN, per tutte le ore passate in discussioni interessanti ed istruttive. Grazie a Tim per tutto quello che mi ha insegnato (protocolli di rete, sistemi di Remote Procedure Call, compilatori, interfacce utente, eccetera) e per aver involontariamente determinato la svolta che mi ha riportato all’università: come studente di informatica, questa volta. Sempre al CERN, sebbene al di fuori del lavoro ufficiale, ho avuto la fortuna di conoscere Giorgio Heiman, Achille Petrilli e Pietro Zanarini: discutendo con loro ho avuto modo di apprendere molte cose utili. Pietro, in particolare, mi ha insegnato che buona parte dei problemi dell’informatica si risolve inventando un linguaggio al giusto livello di astrazione e scrivendo un compilatore o un interprete per detto linguaggio. Prima di arrivare al CERN passai qualche mese studiando il sistema MoniCa (MONItor CAmac), scritto in linguaggio macchina MC680x0 da Horst Von Heicken, allora al CERN. Sono enormemente grato a Horst per tutto quello che ho imparato in questo modo: mi è capitato raramente di vedere codice di così estrema pulizia e chiarezza.

Desidero ringraziare in modo particolare gli insegnanti (e vorrei dire i maestri, ciascuno a modo suo) che ho avuto la fortuna di incontrare nel mio cammino. In ordine di apparizione: Dino Pieri, Attilio Forino, Giorgio Levi, Fabrizio Luccio, Marco Vanneschi e Simone Martini. Essi sono responsabili in parte del grande desiderio che ho di insegnare, il che costituisce il principale motivo per il quale mi sono imbarcato in questa avventura.

Grazie a tutti gli amici del dipartimento di informatica dell’università di Pisa. In particolare, grazie (per vari motivi) a Bruno Bacci, Piero Bonatti, Alessio Bragadini, Marco Comini, Simone Contiero, Marco Danelutto, Pierpaolo Degano, Andrea Dell’Amico, Agostino Dovier, Moreno Falaschi, Paolo Ferragina, Tito Flagella, Luca Francesconi, Maurizio Gabbrielli, Roberto Giacobazzi, Francesca Levi, Paolo Mancarella, Andrea Masini, Dino Pedreschi, Susanna Pelagatti, Corrado Priami, Paola Quaglia, Alessandra Raffaetà, Francesca Rossi, Francesca Scozzari, Laura Semini, Alessandro Sperduti, Stefano Suin, Franco Turini, e Paolo Volpe. Un ringraziamento speciale va a Letizia Petrellese per l’ottimo lavoro che svolge al dipartimento, e soprattutto per quell’attitudine preziosissima che consiste nel farsi carico dei problemi in prima persona. Grazie anche a Rita Cantini per la cortesia e per la disponibilità.

Il ringraziamento al mio supervisore, Giorgio Levi, merita un discorso a parte. Quando iniziai il dottorato di ricerca mi fu profetizzato che sarei stato oggetto di sfruttamento e soprattutto da parte del mio supervisore, chiunque fosse stato, per il motivo che così è, così è sempre stato e così sempre sarà. Al di là della profezia, le voci sullo sfruttamento più o meno selvaggio dei dottorandi sono una costante della vita universitaria. Ebbene, al termine di questi quattro anni desidero rendere testimonianza a Giorgio del fatto che egli non mi ha mai, mai, mai una sola volta sfruttato in alcuna maniera. Al contrario, egli mi ha sempre supportato, sostenuto ed aiutato in ogni modo possibile. Non mi ha mai chiesto di fare nulla che non fosse nel mio interesse ed è sempre stato, prima di tutto, un amico, sia nell’accordo che nel disaccordo.^{2}

Grazie a Kim Marriott per avermi invitato a passare un mese, che è stato di grande ispirazione, in visita alla Monash University di Melbourne. Grazie a Enea Zaffanella per il lavoro e le discussioni fatte insieme, e per l’aiuto che mi ha dato in varie occasioni. Grazie a Maria Cristina Scudellari per il lavoro fatto insieme sul materiale qui riportato al capitolo 7. Grazie a tutti coloro che hanno letto parti di questa tesi per la loro attenzione e i loro suggerimenti: Maurizio Gabbrielli, Roberto Giacobazzi, Patricia M. Hill, Giorgio Levi, Catiuscia Palamidessi, Francesca Rossi, Vijay Saraswat ed Enea Zaffanella. Grazie a Tony Cohn e Pat Hill per avermi consentito di finire questo lavoro di tesi con tranquillità.

Grazie ai miei revisori esterni, Andrew M. King e William H. Winsborough, per la cura con cui hanno svolto il loro lavoro e per avermi letteralmente sommerso di commenti e consigli utili.

2Dicendo questo non intendo assolutamente sostenere che lo sfruttamento dei dottorandi non esista, ma, semplicemente, che la mia esperienza personale è di segno totalmente contrario.

Un grazie particolare a mio fratello Abramo che mi è sempre stato vicino in questi 18 anni di informatica: con lui e da lui ho imparato tanto. In particolare, Abramo mi aiutato moltissimo nello sviluppo di China.

Grazie a Rossana per essermi stata vicina, per avermi incoraggiato, e per aver sopportato, in questi ultimi mesi, i miei assurdi ritmi di lavoro ed il mio umore altalenante.

Le mie scuse, infine, a tutti coloro che ho trascurato durante la stesura di questa tesi.

Roberto Bagnara

Suvereto, 26 febbraio 1997

Si sopravvaluta facilmente l’importanza del proprio dire e fare, rispetto a ciò che uno è diventato

solo grazie agli altri.

— DIETRICH BONHOEFFER, da Resistenza e Resa: lettere e scritti dal carcere (1943–1945) viii

Contents

1 Introduction 1 1.1 The Objective . . . . . . . . . . . . . . . . . . . . . . 1 1.2 The Contents . . . . . . . . . . . . . . . . . . . . . . 13

1.2.1 How It All Started . . . . . . . . . . . . . . . 13 1.2.2 Plan of the Thesis . . . . . . . . . . . . . . . 16

1.3 The Approach . . . . . . . . . . . . . . . . . . . . . . 21

2 Preliminaries 25 2.1 Sets . . . . . . . . . . . . . . . . . . . . . . . . . . . 26 2.2 Some Predefined Sets . . . . . . . . . . . . . . . . . . 26 2.3 Multisets . . . . . . . . . . . . . . . . . . . . . . . . 26 2.4 Cartesian Products and Sequences . . . . . . . . . . 27 2.5 Binary Relations . . . . . . . . . . . . . . . . . . . . 28 2.6 Preorders, Partial and Total Orders . . . . . . . . . 29 2.7 Lattices and Semilattices . . . . . . . . . . . . . . . . 29 2.8 Closure and Kernel Operators . . . . . . . . . . . . . 30 2.9 Monoids . . . . . . . . . . . . . . . . . . . . . . . . . 31

3 A Hierarchy of Constraint Systems 33 3.1 Introduction . . . . . . . . . . . . . . . . . . . . . . . 34 3.2 A Case Study: CLP . . . . . . . . . . . . . . . . . . 37

3.2.1 CLP: the Syntax . . . . . . . . . . . . . . . . 37 3.2.2 Non-Standard Semantics for CLP . . . . . . . 40 3.2.3 Constraint Systems . . . . . . . . . . . . . . . 46 3.2.4 Generalized Semantics . . . . . . . . . . . . . 53 3.2.5 Dealing with Non-Standard Semantics …. 57

3.3 Simple Constraint Systems . . . . . . . . . . . . . . 64 3.3.1 The Atomic Simple Constraint System …. 66 3.3.2 A Simple Constraint System for Simple Types 67 3.3.3 The Herbrand Simple Constraint System . . 68 3.3.4 Bounds and Relations Analysis . . . . . . . . 72

3.4 Determinate Constraint Systems . . . . . . . . . . . 75 3.4.1 Definiteness Analysis: Con . . . . . . . . . . 78 3.4.2 The Pattern Domain . . . . . . . . . . . . . . 79

3.5 Powerset Constraint Systems . . . . . . . . . . . . . 80 3.5.1 A Collecting Semantics for Logic Programs . 86 3.5.2 Structural Analysis: More than Pattern … 86

3.6 Ask-and-Tell Constraint Systems . . . . . . . . . . . 87 3.6.1 Merge Operators . . . . . . . . . . . . . . . . 97 3.6.2 More Bounds and Relations Analysis for Nu-

meric Domains . . . . . . . . . . . . . . . . . 109 3.6.3 Definiteness Analysis: Def . . . . . . . . . . 110 3.6.4 Definiteness Analysis: More than Pos …. 111

3.7 Combination of Domains . . . . . . . . . . . . . . . . 112 3.7.1 Product Constraint Systems . . . . . . . . . . 113 3.7.2 Approximating Built-ins Behavior . . . . . . 118

3.8 Conclusion and Future Work . . . . . . . . . . . . . 119

4 Structural Information Analysis 121 4.1 Introduction . . . . . . . . . . . . . . . . . . . . . . . 122 4.2 Preliminaries . . . . . . . . . . . . . . . . . . . . . . 127 4.3 Factoring Out Structural Information . . . . . . . . 129 4.4 Parametric Structural Analysis . . . . . . . . . . . . 133 4.5 Operations for the Analysis . . . . . . . . . . . . . . 135 4.5.1 Meet with Renaming Apart . . . . . . . . . . 136 4.5.2 Unification with Occur-Check . . . . . . . . . 137 4.5.3 Projection . . . . . . . . . . . . . . . . . . . . 141 4.5.4 Remapping . . . . . . . . . . . . . . . . . . . 142 4.5.5 Join and Widenings . . . . . . . . . . . . . . 144 4.5.6 Comparing Descriptions . . . . . . . . . . . . 146

4.6 What if the Occur-Check is Omitted? . . . . . . . . 148

4.6.1 Unification without Occur-Check . . . . . . . 152 4.7 Conclusion . . . . . . . . . . . . . . . . . . . . . . . 154

5 Range and Relations Analysis 157 5.1 Introduction . . . . . . . . . . . . . . . . . . . . . . . 158 5.2 What Redundant Constraints Are For . . . . . . . . 163

5.2.1 Domain Reduction . . . . . . . . . . . . . . . 165 5.2.2 Extracting Determinacy . . . . . . . . . . . . 167 5.2.3 Static Call Graph Simplification . . . . . . . 170 5.2.4 Future-Redundant Constraints . . . . . . . . 174 5.2.5 Improving any Other Analysis . . . . . . . . 175

5.3 Numbers as Leaves of Terms . . . . . . . . . . . . . 177 5.4 A Sequence of Approximations . . . . . . . . . . . . 178 5.5 Approximations for Sets of Reals . . . . . . . . . . . 184

5.5.1 Intervals . . . . . . . . . . . . . . . . . . . . . 187 5.6 Approximations for Cardinalities . . . . . . . . . . . 191

5.6.1 An Example . . . . . . . . . . . . . . . . . . 191 5.7 Approximations for Numerical Relations . . . . . . . 194

5.7.1 Ordering Relationships . . . . . . . . . . . . . 195 5.7.2 Bounded Differences . . . . . . . . . . . . . . 197 5.7.3 Bounded Quotients . . . . . . . . . . . . . . . 199

5.8 Approximations are Constraints . . . . . . . . . . . . 200 5.9 Implicit Agents . . . . . . . . . . . . . . . . . . . . . 211

5.9.1 Transitive Closure . . . . . . . . . . . . . . . 211 5.9.2 Quantity Refinement . . . . . . . . . . . . . . 212

5.9.3 Numeric Constraint Propagation . . . . . . . 214 5.10 Numeric Agents . . . . . . . . . . . . . . . . . . . . . 214

5.10.1 Cardinality Agents . . . . . . . . . . . . . . . 216 5.10.2 Constraint Agents . . . . . . . . . . . . . . . 217 5.10.3 Quantity Arithmetic Agents . . . . . . . . . . 219 5.10.4 Linear Refinement Agents . . . . . . . . . . . 220 5.10.5 Relational Arithmetic Agents . . . . . . . . . 222

5.11 Binding Agents . . . . . . . . . . . . . . . . . . . . . 223 5.12 Making It Practical . . . . . . . . . . . . . . . . . . . 227

5.12.1 Representing Basic Constraints and Implicit

Agents . . . . . . . . . . . . . . . . . . . . . . 228 5.12.2 Widenings . . . . . . . . . . . . . . . . . . . . 233

5.13 Conclusion . . . . . . . . . . . . . . . . . . . . . . . 234

6 Definiteness Analysis 237 6.1 Introduction . . . . . . . . . . . . . . . . . . . . . . . 237 6.2 Boolean Functions for Groundness Analysis . . . . . 240 6.3 Combination of Domains and Reactivity . . . . . . . 242 6.4 Binary Decision Trees and Diagrams . . . . . . . . . 246 6.5 Is x Ground? . . . . . . . . . . . . . . . . . . . . . . 249 6.6 Extracting Sure Groundness from ROBDDs . . . . . 250 6.7 A New, Hybrid Implementation for Pos . . . . . . . 255 6.8 Experimental Evaluation . . . . . . . . . . . . . . . . 262 6.9 Conclusion . . . . . . . . . . . . . . . . . . . . . . . 266

7 Precise Detection of Call-Patterns 267 7.1 Introduction . . . . . . . . . . . . . . . . . . . . . . . 268 7.2 The Magic-Templates Algorithm . . . . . . . . . . . 274 7.3 Generalized Semantics . . . . . . . . . . . . . . . . . 276

7.3.1 Interpretation Domains . . . . . . . . . . . . 276 7.3.2 Functional Representations . . . . . . . . . . 281 7.3.3 Generalized CLP Programs . . . . . . . . . . 285 7.3.4 The Functional Semantics . . . . . . . . . . . 286 7.3.5 Top-Down (Operational) Construction …. 286 7.3.6 Bottom-Up (Fixpoint) Construction . . . . . 289

7.4 Abstract Interpretation . . . . . . . . . . . . . . . . 296 7.5 Conclusion . . . . . . . . . . . . . . . . . . . . . . . 299 7.6 Proof of the Main Result . . . . . . . . . . . . . . . 299

8 Conclusion 343

Bibliography 347 xiv

List of Figures

3.1 A lattice of simple types. . . . . . . . . . . . . . . . 68 3.2 Reduction rules for finite cc agents. . . . . . . . . . . 91 3.3 The semantic normal form does not help deciding the

entailment. . . . . . . . . . . . . . . . . . . . . . . . 98

4.1 List-length in CLP(H, N). . . . . . . . . . . . . . . 122 4.2 The fill_rectangle program. . . . . . . . . . . . . 123 4.3 A fragment of csg.clpr. . . . . . . . . . . . . . . . 124 4.4 Upgrading a domain with structural information… 132 4.5 The rational tree that solves X = f(X, Y). . . . . . 150

5.1 Sum of the first N naturals in CLP(N). . . . . . . . . 161 5.2 McCarthy’s 91-function in CLP(N). . . . . . . . . . 166 5.3 Fibonacci’s sequence in CLP(N). . . . . . . . . . . . 169 5.4 Fragment of WAM-like code for the 3rd clause of fib

to be executed when the 1st argument is not definite. 172 5.5 Standard mortgage relationship in CLP(R). . . . . . 174 5.6 A bijection (ĩ, j̃): I_{m} → {1, …, m(m − 1)/2}…. 183 5.7 Hasse diagram of the cardinalities’ lattice. . . . . . . 192 5.8 Hasse diagram of the ordering relationships lattice. . 196

6.1 OBDTs for (x ∧ y) ↔ z (a) and (x ↔ z) ∧ y (b)… 246 7.1 Program showing some difficulties with the magic

transformation. . . . . . . . . . . . . . . . . . . . . . 272 7.2 The “magic version” of the program in Figure 7.1. . 277 7.3 The “magic version” of the program in Figure 5.2 on

page 166. . . . . . . . . . . . . . . . . . . . . . . . . 277

List of Tables

5.1 The ⊞ operator on cardinalities. . . . . . . . . . . . 193 5.2 The ⊟_{2} operator on cardinalities: x ⊟ y is at the

intersection between the x-th row and the y-th column.193 5.3 The ⧇ operator on ordering relationships. . . . . . . 196

6.1 Operations defined over B_{o} and G. . . . . . . . . . . 257 6.2 Experimental results obtained with the China ana-

lyzer. . . . . . . . . . . . . . . . . . . . . . . . . . . . 264 xviii

List of Algorithms

1 Unification for the parametric structural domain… 139 2 Revised unification for the structural domain. . . . . 152 3 Naive algorithm for transitive closure. . . . . . . . . 230 4 Naive algorithm for quantity refinement. . . . . . . . 230 5 AC-3 algorithm for quantity refinement. . . . . . . . 232 6 Algorithm for numeric-constraint-propagation. . . . . 232 xx

Chapter 1

Introduction

\underline{Contents}

1.1 The Objective . . . . . . . . . . . . . . . . 1 1.2 The Contents . . . . . . . . . . . . . . . . . 13 1.2.1 How It All Started . . . . . . . . . . . . 13 1.2.2 Plan of the Thesis . . . . . . . . . . . . 16 1.3 The Approach . . . . . . . . . . . . . . . . 21

1.1 The Objective

This study is aimed at the development of precise, practical, and theoretically well-founded data-flow analyzers for constraint logicbased languages. The emphasis on the above words is important for a full understanding of what follows, since some of the author’s personal views and expectations are hidden behind them. Thus, in order to make our aims clear from the very beginning, we should start explaining the exact meaning of each word. This can be better achieved by treating them in reverse order.

Constraint. Many problems can be conveniently expressed, reasoned about, and solved by means of constraints. These problems range from temporal, spatial, and physical reasoning, to image processing, automated diagnosis, plus all the problems that are typical of the fields of operation research and mathematical programming. As we will see, an important class of data-flow analyses for constraint logic-based languages can also be easily understood in terms of constraint manipulation.

The use of constraints as a description language and of techniques based on constraints has a long history in the field of Artificial Intelligence. Many constraint-based applications have been developed in the last 30 years in order to explore and solve specific problems. These systems, however, were often ad hoc systems with very little in common among them. Eventually, researchers realized that constraints could allow for programming machines in a novel way: constraint programming.^{1}

Logic-Based Languages. Those programming paradigms that are “based on logic” are the best suited for constraint programming. Indeed, viewing basic constraints as atomic formulae in first-order predicate logic is a natural thing to do. Logic-based languages then provide logical and meta-logical ways of composing and dealing with constraints.

A significant part of the material contained in the present thesis deals with constraints and ways of approximating them by means of other constraints. Thus, several results are of interest independently from the considered programming paradigm. Nonetheless we focus our attention on the “constraint logic programming” paradigm for reasons that will soon be explained.

1An embryonic notion of constraint programming goes back to the Sketchpad system of Sutherland: an interactive system where complex graphic objects could be specified by imposing constraints on the various attributes of the objects themselves [Sut63]. One of the first examples of true constraint programming languages is Constraints, by Sussman and Steele [SS80].

Constraint logic programming (CLP) is a generalization of the pure logic programming paradigm (LP), having very similar modeltheoretic, fixpoint and operational semantics [JL87, JM94]. It embodies a quite natural view of “constraint computations” expressed by definite clauses. The CLP notion of constraint solving in a given algebraic structure encompasses the one of unification over some Herbrand universe. This gives CLP languages a great advantage over LP in terms of expressivity and flexibility.

We believe that CLP languages have a future, and this is the first reason for our particular interest. They provide elegant and simple linguistic support for constraint programming. This makes them suitable for a number of applications. Several CLP languages and systems exist to date, both in academic and industrial installations. Thus, CLP languages now have a significant user community. As a consequence we have a rough idea as to what a constraint logic program looks like: for a project that aims at some experimental validation of the theoretical ideas, this is an important factor.

Another motivation for studying data-flow analysis of CLP languages is that such languages can benefit from the analysis’ results more than other paradigms. Both CLP compilers and programmers can do a much better job if they are given information about the run-time behavior of programs.

In some cases CLP programs can be naturally more efficient than the correspondent LP ones, because of the possibility of reasoning directly in the “domain of discourse” (integer arithmetic, for instance), without requiring complicated encodings of data objects as first-order terms. However, the basic operational step in CLP program execution, a test for solvability of constraints, is generally far more complex than unification. Ideally, a correct implementation of a CLP language needs a complete solver, that is a full decision procedure for the satisfiability of constraints in the language’s domain(s).^{2} The indiscriminate use of such complete solvers in their full generality can lead to severe inefficiencies. For these reasons, the optimized compilation of CLP programs can give rise to impressive performance improvements, even more impressive than the ones obtainable for the compilation of Prolog.

The CLP paradigm inherits from LP the complete freedom that programmers have when writing their program. Usually there are no prescriptive declarations (types, for instance) and any program that is syntactically correct is a legal one. Even though this is a much debated point, absolute freedom makes CLP languages attractive for the so called fast-prototyping of applications. The back side of the coin is that there is not much the compiler can do in order to help programmers and maintainers during the various phases of the software life-cycle. This clearly poses serious software engineering problems and, as a matter of fact, many prototypes do not evolve into real applications. The good news is that (constraint) logic programming, due to its unique semantic features, has a unique potential for formal program development. This possibility can be turned into a reality by providing a set of semantics-based tools (i.e., based on program analysis) for writing, debugging, manipulating, and reasoning about programs.

In summary, data-flow analysis has a great potential for providing valuable information to both the compiler and the developer of CLP programs. It promises to play a fundamental role in the achievement of the last point in the following wish list for CLP:

(1) retaining the declarativity and flexibility of logic programming; (2) augmenting expressivity by allowing direct programming in the intended computational domain; (3) gaining a competitive advantage, in terms of efficiency, productivity, and maintainability, over other programming paradigms.

2Strictly speaking, this is not true, as the hypothesis of completeness of the constraint solver can be relaxed, still obtaining useful systems [JM94, BCSZ96].

However, if the system does not employ a complete solver the user must be prepared to receive “answer constraints” that are inconsistent. Thus, a complete solver is required anyway, sooner or later.

Data-Flow Analyzers. A data-flow analyzer is a computer program: it takes a program P as its input and, after a reasonable amount of time, it delivers some partial information about the runtime behavior of P. As such, a data-flow analyzer is quite different from that which is usually meant by “a data-flow analysis”: a collection of techniques by which one can, in principle, derive the desired information about the behavior of programs. While it is undoubtedly true that a data-flow analyzer implements some dataflow analyses, the standard connotations of the two terms allow for a huge gap in the level of detail that one has to provide.

Designing a data-flow analysis requires a significant effort. The theory of abstract interpretation [CC77, CC92b] is a blessing in this respect, as it provides a variety of frameworks for ensuring the correctness of the analysis, once you have defined a domain of approximate program properties. The theory also provides standard ways of composing existing domains, reasoning about them, and deriving approximate versions of the relevant operations of the language at hand. However, the theory does not tell you how to invent an abstract domain that represents a reasonable compromise between precision and efficiency for the class of programs being analyzed. This is what makes the design of data-flow analyses potentially hard, depending on the use one has in mind for them.

If the purpose of the analysis is to write a paper, a number of shortcuts are possible. Some of them are completely justified by the needs of the presentation: cluttering the description with unnecessary details is counterproductive. The author’s definition for “unnecessary detail” is: something that can be easily filled in by the interested reader without breaking any result in the paper. Other shortcuts are more questionable. Here is an incomplete categorization of things that make life easier.

• Pretend that the language to be analyzed is not what it is. Assuming that, in CLP languages, program variables and constraints are typed is an example. If one takes CLP(R), for instance, a clause can contain a variable X and absolutely no indication of whether X is a numerical variable or not. There is no typing at all, indeed: in some execution paths X can be a numerical variable whereas in some other paths X is bound to an uninterpreted functor. The same thing happens for constraints of the form X = Y and for implicit constraints that are determined by repeated occurrences of variables in the head of clauses, like p(X, X): sometimes X = Y denotes arithmetic equality, sometimes it is an unification constraint.

Pretending that implemented logic-based languages perform unification without omitting the occur-check is another notable example of this kind of sin. In the literature on data-flow analysis this problem is systematically swept under the carpet, even in papers dealing with the analysis of Prolog (which, proverbially, omits the occur-check).

• Assume that it can easily be implemented. Excessive use of semantic notions, for instance, can be the source of difficulties. The reduced product, for example, is a nice conceptual device that is applicable in many cases. However, it is defined in terms of the concretization function. As a consequence, representing and computing some canonical form of the reduced elements may turn out to be impractical. There are indeed cases where one must content oneself with an approximation of the reduced product. Still on this subject, specifying an abstract domain as a space of (upper or lower) closure operators on some complete lattice usually allows for elegant formalizations. It is important not to forget that a closure operator is a Platonic object: sooner or later it will have to be represented on the machine, and this can pose serious problems.

• Disregard efficiency. This is not so bad, in general, as the situation can be redressed by resorting to widening operators. The problem of designing widening operators and of determining where and when to apply them must not be underestimated.

• Compromise precision. The opposite mistake. A worse one, indeed: too much anxiety about efficiency can lead to the selection of analysis frameworks that lose too much information from the very beginning. A state of things that has few chances of being fixed.

• Pretend that the programs to be analyzed are reasonable. For instance, that no program clause contains more than, say, 64 variables. Or that programs do not use “nasty constructs” such as assert/1. After all, if a program turns out to be unreasonable the analysis can answer “don’t know”: this is always a legitimate thing to do in data-flow analysis.

In the author’s experience, the researcher who engages in the task of designing a data-flow analysis with reasonable chances of being successfully implementable must be prepared to have a bad time. The mistakes that have been outlined above can trap him at any stage. Furthermore, during the pencil-and-paper development many questions arise naturally, and most of them cannot be answered. One typical form of such questions is the following: “am I sure that this is reasonably implementable?” Sometimes the answer comes later in the actual implementation stage. If the answer is negative some other solution has to be found. The implementor will have to conduct his own research, and an analysis distinct from the one proposed by the designer will have to be implemented.

Another class of questions is: “how much am I going to gain if I do that, for the average program?” For relatively new programming paradigms such as CLP the notion of average program makes little sense, which, again, means that there can be no answer. Thus, the risk of spending much time refining an analysis so as to make it implementable, and later discovering that relatively few programs may benefit from the results, is high.

In an attempt to circumvent these difficulties, we started our work with analyzers, not just analyses, in mind. In other words, our interest is in “implementable data-flow analyses”, or, stated differently, “data-flow analyses that are actually implemented by analyzers”. There is no free lunch, of course: experience has shown that dealing with analyzers, while perhaps saving you from overlooking important aspects of the problem, forebode a plethora of other troubles. This, however, is another story. By the way, the name of the author’s pet analyzer is China^{3}. China is a dataflow analyzer for CLP languages based on the abstract compilation approach. It performs bottom-up analysis deriving information on both call- and success-patterns by means of program transformations and optimized fixpoint computation techniques. It currently supports combined domains for: (parametric) structural information, simple types, definiteness, aliasing and freeness, numerical bounds and relations of and among numerical leaves, and polymorphic types. China consists of about 40,000 lines of C^{++} code for the abstract interpreter, and about 5,000 lines of Prolog code for the abstract compiler.

Theoretically Well-Founded. The approach of putting oneself in the implementor’s shoes, of course, must not be to the detriment of a careful study of the correctness issues. This requires, besides firm semantic bases, formal methodologies to ensure the correctness of the analysis. Abstract interpretation is the framework of choice for this purpose.

3Clp(Herbrand Integrated-with Numeric-domains) Analyzer.

It is the author’s impression that too often the importance ascribed to a result is directly proportional to the strength of its conclusion. The ratio between the strength of the conclusion and the strength of the premises should, instead, be considered as the right measure. A weak conclusion that can be established starting from weak premises can be very valuable. This is especially the case when one deals with real implementations where, in order to tackle the various complexity precision trade-offs, one needs as much freedom as possible. Often this implies that the strong premises cannot be ensured, while weak conclusions would be enough. Scalable theories are particularly precious in this respect. The author’s favorite reference for abstract interpretation is Abstract Interpretation Frameworks, by Cousot and Cousot [CC92b]. There, the authors start from very weak hypotheses that only guarantee correctness and finiteness of the analysis. Only later Galois connections and insertions are introduced in a blaze of glory, with all their nice properties.

Practical. A “practical analyzer” is one that has a chance to be turned into a useful tool. On one side this means that compromising assumptions about the languages and the programs must be avoided as much as possible. On the other side, potential efficiency must be taken into consideration. However, striving too much for efficiency should be avoided. In the literature there are papers reporting on the experimental evaluation of data-flow analyzers. In some of them one can find analysis’ times well under the second for non-trivial, lengthy programs. It is true that some of these papers have, among their objectives, the praiseworthy one of convincing the reader of the practicality of data-flow analysis. Nonetheless the author finds these results a bit disconcerting. What can one conclude from the fact that a program of some hundreds lines can be analyzed in a couple of seconds?

Hypothesis 1: That the problem of data-flow analysis for logic programs has been brilliantly solved once and for all?

Hypothesis 2: That special assumptions have been exploited so that the results cannot be generalized?

Hypothesis 3: That much more precision is attainable?

The first hypothesis is hardly credible. In the case of Prolog, the effectiveness of analysis has been demonstrated experimentally by Parma [Tay90] and Aquarius Prolog [VD92]. Impressive speedups have been reported. And today’s analyzers are far more precise and efficient than those employed in Parma and Aquarius Prolog. Despite this fact, to the author’s knowledge no known Prolog system to date, whether existing or being developed, includes a global data-flow analysis stage to drive the optimization phase. Is this an indication that at least somebody does not believe in Hypothesis 1? Probably yes, and probably he is right.^{4}

4Since there are serious indications that the logic programming community is not fond of itself, the answer cannot be: yes, definitely. Some explanations are in order. On the vast majority of Prolog implementations (here included those who claim to have been industrialized) the user has to wait for a run-time exception just to know that he has misspelled a (non-dynamic) predicate name. A run-time error (or, if lucky, a mild warning) is all one can hope for in cases of built-in misuse like p(ArgNo, Term, Arg) :- arg(Argno, Term, Arg). By the way, normally run-time errors come without any indication about which clause was executed when the erroneous situation occurred. Sometimes we really wonder if this does not unveil some masochism on the part of the logic programming community. The fact that we are Programming in Logic is no excuse for having less tools at our disposal than those Programming in Assembly!

Hypothesis 2 has already been discussed. Whenever it applies we are faced with the research problem of removing any limiting assumption.

The author favors the last hypothesis. A couple of seconds is a ridiculous time for, say, optimized compilation. If one takes into account that

1. only production versions deserve to be compiled with the optimization pass turned on, and

2. a production version is compiled once and used thousands, perhaps millions of times,

then it is clear that analysis times remain totally acceptable even if multiplied by a factor of 50 or 100.^{5} Then the research problem is: how can we make profitable use of the extra-time that users are willing to pay for? Performing other analyses is one answer, increasing the precision of existing analyses is another possibility.

Precise. The author does not share the view that only fast analyses with immediate, consistent payoffs in optimized compilation deserve to be studied. There are other applications of data-flow analysis, such as semantics-based programming environments, where one probably needs very precise information about programs’ behavior in order, say, to assist the programmer during the development.

5Some people may disagree with this assertion. The author’s opinion is motivated by the following (admittedly subjective) facts: the China system takes 30 minutes to compile without optimization; with full optimization it takes around 3 hours; China has been compiled in optimized mode perhaps 10 times during the last year, always overnight; the author would be delighted to have a C^{++} compiler delivering a 10% speedup on the execution of China in exchange for 12 hours (the entire night) of compilation time. Last but not least, software houses can afford machines that are 20 times faster than the author’s PC. And the duration of the night is the same for them, too!

Ironically, in this field the efficiency concerns are much more stringent, if one aims at interactive development tools.

So we should go for more precision. The problem is how to increase precision yet avoiding the concrete effects of exponential complexity. Consider groundness analysis, for instance. The cruder domains do not pose any efficiency problem. In contrast, the more refined domains for groundness, such as Pos, work perfectly until you bump into a program clause with more than, say, fifty variables. At that time, Pos will blow-up your computer’s memory. One would like to have a more linear, or stable behavior. The right solution, as indicated by Cousot and Cousot [CC92c], is not to revert to the simpler domains. We should use complex domains instead, together with sophisticated widening/narrowing operators. With this technique we can try to limit precision losses to those cases where we cannot afford the complexity implied by the refined domains.

Ideally, it should be possible to endow data-flow analyzers with a knob. The user could then “rotate the knob” in order to control the complexity/precision ratio of the system. The widening/narrowing approach can make this possibility a reality. Unfortunately, the design of widening operators tends somewhat to escape the realm of theoretical analysis, and thus, in the author’s opinion, it has not be studied enough. Indeed, the development of successful widening operators requires, perhaps more than other things, extensive experimentation.

Development. This word is emphasized just to warn the reader that the above description is about the ideas and aspirations that drove our work, not about what we have already achieved. There, is where we want to go; and it is far away from where we are now.

1.2 The Contents

In this thesis you can find an account of the author’s research from March 1993 to October 1996. The work included here is all centered around the development of the China analyzer. By this we mean:

1. that almost any theoretical study we have undertaken was suggested by difficulties in the design or implementation of China;

2. that China has been used to experimentally validate, as much as possible, several ideas that appeared to be nice in theory.

The author believes that the thesis should be of interest to all those involved in data-flow analysis and semantics-based program manipulation of constraint programming languages. A considerable effort has been made in making the presentation as readable as possible: the reader must judge to what extent we succeeded.

1.2.1 How It All Started

Some history is required so as to put the subsequent material into context. Also, since some chapters in this thesis, or their leading ideas, are based on papers that have already been published, we take the occasion to report here the relevant bibliographic sources.

We tackled the problem of data-flow analysis of CLP languages over finite numeric domains back in 1992. In [Bag92] we proposed an abstract interpretation deriving spatial approximations of the success set of program predicates. The concrete interpretation of a constraint, that is, a shape in k-dimensional space, was abstracted by an enclosing, though not necessarily minimal, bounding box. Bounding boxes are just rectangular regions with sides parallel to the axes. Thus, a bounding box is univocally identified by its projections (i.e., intervals) over the axis associated to the variables. Of course, bounding boxes are very coarse approximations of general shapes. Finer spatial approximations exist and are well-known, such as enclosing convex polyhedra, grid and Z-order approximations [HMO91] and so on. However, the coarseness of bounding boxes is well repaid by the relative facility with which they can be derived, manipulated and used to deduce information relevant to static analysis. There are several techniques for deriving bounding boxes from a given set of arithmetic constraints (especially if one confines attention to linear constraints): variants of Fourier-Motzkin variable elimination [DE73, Pug92], the sup-inf method [Ble74, Sho77, Sho81] etc. In [Bag92] we considered a variant of the constraint propagation technique called label inference with interval labels [Dav87]. More precisely we used the Waltz algorithm [Wal75, Mac77, MF85] applying label refinement, in which the bounding box corresponding to a set of constraints is gradually restricted.

Later, we turned our attention to the analysis and compilation issues of languages such as CLP(R). In particular, we studied a notion of future redundant constraint less restrictive than the one introduced by Jørgensen et al. [JMM91]. An analysis for the detection of implicit numeric constraints, future redundant ones in particular, was sketched by Giacobazzi, Levi and the author in [BGL92], and the constraint propagation techniques used, collectively known as constraint inference [Dav87], were presented in [BGL93]. Meanwhile, the development of China’s first prototype was started. The applications of implicit and redundant constraint analysis were first described in [Bag94], together with some experimental results obtained with the prototype analyzer.

The formalization of the (concrete and abstract) domains and of the analysis was unsatisfactory. What characterizes a numeric constraint? A possible answer is “the set of its solutions”. Some authors have formalized concrete domains that way [GDL92]. But this does not work in general as, in order to constructively define an abstraction function you must know the solutions. Unfortunately this is unfeasible or even impossible, in general: solving a set of equations of the form (x_{i}−x_{j})^{2}+(y_{i}−y_{j})^{2} = c_{ij} is NP-hard [Yem79], whereas it is undecidable whether a transcendental expression is equal to zero [Ric68]. As a final example, solving a set of linear integer constraints with at most two variables per inequality is NPcomplete [Lag85].

For these reasons we chose to define a constraint as “the set of its consequences under some consequence relation”. This is a much more reasonable definition as it allows to capture both the concrete domains really implemented by CLP systems and the class of approximations we were considering. We were thus lead to the study of partial information systems [Sco82] and constraint systems [SRP91]. Another theoretical difficulty was due to the fact that our numerical domain was a combination of two other domains: one for real intervals and the other for ordinal relationships. The combination was something different from the reduced product: sometimes less precise, sometimes more. More importantly, we wanted the combination to be programmable in order to meet different balances between precision and efficiency. As a solution we gave the dignity of constraints to a restricted class of cc agents [SRP91, Sar93]. Some pieces of the theory were missing^{6} and we became deeply involved in the subject. The results appeared first in [Bag95a] and refined in [Bag97].

Meanwhile, the development of the second prototype of China was initiated. At some point we faced the problem of analyzing non-linear constraints for CLP(R). As in CLP(R) non-linears are delayed until they become linear, the analyzer is not allowed to derive anything from them unless the analysis has inferred that they have indeed become linear. Having solved the theoretical difficulties about the combination of domains, we implemented the Pos groundness domain in the standard way, and we combined it with the numerical component. The interaction could be implemented in different ways: the numerical component could ask the definiteness component, unsolicited definiteness information could be sent to the numerical component, or a mixture of the two. In addition, some groundness information that is synthesized in the numerical component should be notified to the definiteness component. However, no one had studied the problem of efficiently detecting ground variables in the context of the standard implementation of Pos. This motivated our interest in groundness analysis, a topic that we believed was almost closed. The outcome of our studies on the subject was briefly sketched in [Bag96c] and later presented in [Bag96b].

6For instance: what is the join of two finite cc agents? Is it representable by means of a finite cc agents? If not (as it is the case), how can we design suitable approximations of the join operation?

The China system is still evolving, and has never previously been described, although a very short description of the first prototype of China is given in [Bag94].

Recently, we have been working on the precise detection of callpatterns by means of program transformation. In a joint work with Maria Cristina Scudellari (a former student at the University of Pisa) we devised a solution. However, this has not undergone experimental evaluation yet. It is presented here for the first time in Chapter 7.

1.2.2 Plan of the Thesis

Chapter 2 introduces some of the necessary mathematical background. It is mostly intended to define the terminology and notation that will be used throughout the thesis.

Chapter 3 is motivated by the fact that many interesting analyses for constraint logic-based languages are aimed at the detection of monotonic properties, that is to say, properties which are preserved as the computation progresses. Our basic claim is that most, if not all, of these analyses can be described within a unified notion of constraint domains. We present a class of constraint systems which allows for a smooth integration within an appropriate framework for the definition of non-standard semantics of constraint logic-based languages. Such a framework is also presented and motivated. We then show how such domains can be built, as well as construction techniques which induce a hierarchy of domains with interesting properties. In particular, we propose a general methodology for domain combination with asynchronous interaction (i.e., the interaction is not necessarily synchronized with the domains’ operations). Following this methodology, interesting combinations of domains can be expressed with all the semantic elegance of concurrent constraint programming languages.

Chapter 4 presents the rational construction of a generic domain for structural analysis of CLP languages: Pattern(D^{♯}), where the parameter D^{♯} is an abstract domain satisfying certain properties. Our domain builds on the parameterized domain for the analysis of Prolog programs Pat(ℜ), which is due to Cortesi et al. [CLV93, CLV94]. However, the formalization of our CLP abstract domain is independent from specific implementation techniques: Pat(ℜ) (slightly extended and corrected) is one of the possible implementations. Reasoning at a higher level of abstraction we are able to appeal to familiar notions of unification theory. One advantage is that we can identify an important parameter (a common anti-instance function, missing in [CLV93]) that gives some control over the precision and computational cost of the resulting generic structural domain. Moreover, we deal —apparently for the first time— with the occur-check problem: while, to our knowledge, all the analysis’ domains that have been proposed in the literature (Pat(ℜ) included) assume that the analyzed language implements complete unification, most real languages do omit the occur-check. This fact has serious implications both in terms of correctness and precision of the analysis.

Chapter 5 addresses the problem of compile-time detection of implicit and redundant numeric constraints in CLP programs. We discuss how this kind of constraints has several important applications in the general field of semantics based program manipulation, and, specifically, optimized compilation. In particular, we propose a novel optimization based on call-graph simplification. We then describe a sequence of approximations for characterizing, by means of ranges and relations, the values of the numerical leaves that appear in Herbrand terms. This, among other things, brings to light the need of information about how many numerical leaves a term has. For each approximation needed for the analysis we offer different alternatives that allow for dealing with the complexity/precision tradeoff in several ways. The overall numeric domain (or, better, a family of them) is obtained (among other things) by means of the product and the ask-and-tell constructions of Chapter 3. The ask-and-tell constraint system constitutes a very convenient formalism for expressing both (1) efficient reasoning techniques originating from the world of artificial intelligence, where approximate deduction holds the spotlight since the origins; and (2) all the abstract operations needed for the analysis. In practice, we define a family of concurrent languages that serve as target-languages in an abstract compilation approach.

Chapter 6 deals with groundness analysis for (constraint) logic programs. This subject has been widely studied, and interesting domains have been proposed. Pos (a domain of Boolean functions) has been recognized as the most suitable domain for capturing the kind of dependencies arising in groundness analysis. Its standard implementation is based on reduced ordered binary-decision diagrams (ROBDDs), a well-known symbolic representation for Boolean functions. Even though several authors have reported positive experiences using ROBDDs for groundness analysis, in the literature there is no reference to the problem of the efficient detection of those variables that are deemed to be ground in the context of a ROBDD. This is not surprising, since most currently implemented analyzers need to derive this information only at the end of the analysis and only for presentation purposes. Things are much different when this information is required during the analysis. This need arises when dealing with languages which employ some sort of delay mechanism, which are typically based on groundness conditions. In these cases, the naïf approaches are too inefficient, since the abstract interpreter must quickly (and often) decide whether a constraint is delayed or not. Fast access to ground variables is also necessary when aliasing analysis is performed using a domain that does not keep track of ground dependencies. We introduce and study the problem, proposing two possible solutions. The second one, besides making possible the quick detection of ground variables, has also the effect of keeping the ROBDDs as small as possible, improving the efficiency of groundness analysis in itself.

Chapter 7 presents some recent work about the analysis of callpatterns for constraint logic programs. In principle, call-patterns can be reconstructed, to a limited extent, from the success-patterns. This, however, often implies significant precision losses. As the precision of call-patterns is very important for many applications, their direct computation is desirable. Top-down analysis methods are usually advocated for this purpose, since the standard execution strategy of (constraint) logic programs is top-down. Alternatively, bottom-up analysis methods based on the Magic Sets or similar transformations can be used [Kan93, CDY94, Nil91]. This approach, however, can result in a loss of precision because the connection between call- and success-patterns is not preserved. In a recent work, Debray and Ramakrishnan [DR94] introduced a bottom-up analysis technique for logic programs based on program transformation. They showed, among other things, that their bottom-up analysis is at least as precise (on both call and success-patterns) as any top-down abstract interpretation using the same abstract domain and abstract operators. The basic idea behind [DR94] is to employ a variation of the Magic Templates algorithm [Ram88]. Moreover, the (possibly approximated or abstract) “meaning” of a program clause (or predicate) is a partial function mapping descriptions of tuples of terms (the arguments at the moment of the invocation: call-patterns) into sets of such descriptions (describing the possible arguments at the return point of that invocation: success-patterns). The solution of [DR94], however, is not generalizable to the entire class of CLP languages, since they exploit the peculiar properties of the Herbrand constraint system, where a tuple of terms is a strong normal form for constraints. This way, and by duplicating the arguments, they are able to describe the partial functions which represent the connection between call and successpatterns in the clauses heads. Of course, this cannot be done for generic CLP languages. Our aim is thus to generalize the overall result of [DR94] to the general case of constraint logic programs: “the abstract interpretation of languages with a top-down execution strategy need not itself be top-down”.

Chapter 8 draws some final conclusion about what we have done and learned. More importantly, it traces some lines for further research describing what remains to be done in order to approach the objectives that have been delineated in this introduction.

1.3 The Approach

As the reader will have already noticed, our approach is based on a mixture of theory and experimentation. In the field of computer science these two aspects of research are too often confined in distinct, separate worlds. About this subject, the “Committee on Academic Careers for Experimental Computer Scientists” of the U.S.A. National Research Council has words that deserve a long quotation [NRC94].

[ …] the crux of the problem is a critical difference in the way the theoretical and experimental research methodologies approach research questions. The problem derives from the enormous complexity that is fundamental to computational problems [ …] This complexity is confronted in the theoretical and experimental research in different ways, as the following oversimplified formulation exhibits.

When presented with a computational problem, a theoretician tries to simplify it to a clean, core question that can be defined with mathematical rigor and analyzed completely. In the simplification, significant parts of the problem may be removed to expose the core question, and simplifying assumptions may be introduced. The goal is to reduce the complexity to a point where it is analytically tractable. As anyone who has tried it knows, theoretical analysis can be extremely difficult, even for apparently straightforward questions.

When presented with a computational problem, an experimentalist tries to decompose it into subproblems, so that each can be solved separately and reassembled for an overall solution. In the decomposition, careful attention is paid to the partitioning so that clean interfaces with controlled interactions remain. The goal is to contain the complexity, and limit the number and variety of mechanisms needed to solve the problem. As anyone who has tried it knows, experimentation can be extremely difficult to get right, requiring science, engineering, and occasionally, good judgment and taste. The distinction between these two methodologies naturally fosters a point of view that looks with disdain the research of the other. When experimentalists consider a problem that has been attacked theoretically and study the related theorems that have been produced, they may see the work as irrelevant. After all, the aspects that were abstracted away embodied critical complicating features of the original problem, and these have not been addressed. The theoretician knows no analysis would have been possible had they been retained, whereas the experimentalist sees that “hard parts” of the problem have been left untouched. Conversely, when theoreticians examine a problem attacked experimentally and spot subproblems for which they recognize theoretical solutions, they may see the work as uninformed and non-scientific. After all, basic, known results of computing have not been applied in this artifact, and so the experimentalist is not doing research, just “hacking.” The experimentalist knows that it is the other aspects of the system that represent the research accomplishment, and the fact that it works by using a “wrong” solution implies that the subproblem could not have been too significant anyway.

So, as by the blind men encountering an elephant, impressions are formed about the significance, integrity, and worth of computing research by its practitioners. Although it is natural for researchers to believe their own methodology is better, no claim of superiority can be sustained by either. Fundamental advances in Computer Science & Engineering have been achieved by both experiment and theory. Recognizing that fact promotes tolerance and reduces tensions.

This excerpt does contain a faithful description of the incommunicability that seems to be so frequent in our field. However, it takes for granted that there are two kinds of computer science researcher: the theoretician and the experimentalist.

The author has done five years of “real programming work” in environments where the theoreticians were often laughed at (and these environments were probably the most illuminated ones among those where real programming takes place). And during the last four years he has done full-time research work in a milieu where experimentalists “look strange”, so to speak. These hostile feelings are sometimes justified. There are, indeed, experimentalists that seem unable to communicate their findings to the other researchers; some even refuse to do it. On the other hand there are theoreticians that like filling the “applications” sections of their papers with conjectures, even though these speculations are not explicitly presented as such.

These observations have convinced the author that being a theoretician and an experimentalist at the same time was something worth a try. The risk of being mediocre in both these activities is high, of course, but we must say that we are satisfied with this choice. Having tried, our impression is that theoretical and experimental work do have synergetic effects. As it has already been mentioned, most of our theoretical studies were suggested by practical difficulties. Moreover, the author shamelessly admits that several deficiencies in his theories have been discovered either at the time of writing the actual code (do not know what to write: an under-specified theory), or by observing the results obtained with the implementation (a wrong theory).

If you try to write for the novice you will communicate with the experts;

otherwise you will communicate with nobody.

— DONALD E. KNUTH, lecturing on Mathematical Writing, Stanford University (1987)

Preliminaries

\underline{Contents}

2.1 Sets . . . . . . . . . . . . . . . . . . . . . . . 26 2.2 Some Predefined Sets . . . . . . . . . . . . 26 2.3 Multisets . . . . . . . . . . . . . . . . . . . 26 2.4 Cartesian Products and Sequences …. 27 2.5 Binary Relations . . . . . . . . . . . . . . . 28 2.6 Preorders, Partial and Total Orders … 29 2.7 Lattices and Semilattices . . . . . . . . . . 29 2.8 Closure and Kernel Operators . . . . . . . 30 2.9 Monoids . . . . . . . . . . . . . . . . . . . . 31

In this chapter we introduce the mathematical concepts and notations that will be used throughout the thesis. The intention is to setup a common language with the reader, and not to explain these concepts in detail. Thus, throughout the thesis we will assume familiarity with the basic notions of lattice theory, semantics of logic programming languages, and abstract interpretation.

2.1 Sets

Let U be a set. The set of all subsets of U will be denoted by ℘(U). The set of all finite subsets of U will be denoted by ℘_{f}(U). The notation S ⊆_{f} T stands for S ∈ ℘_{f}(T). For S ⊆ U we will denote the complement U \ S by \overline{S}, when U is clear from the context. For S, T ⊆ U the notation S ⊎ T denotes disjoint union, emphasizing the fact that S ∩ T = ∅.

2.2 Some Predefined Sets

The sets of all natural, integer, rational, real, and ordinal numbers will be denoted, respectively, by ℕ, ℤ, ℚ, ℝ, and 𝕆. The first limit ordinal equipotent with the set of natural numbers is denoted by ω.

2.3 Multisets

A multiset is a mathematical entity that is like a set except for the fact that it can contain identical elements a finite number of times [Knu80]. A multiset can be specified by listing all the occurrences of its elements. In order to distinguish multisets from sets, special parentheses are used for this purpose. For instance,

⟅a, a, b, c, c, c⟆

denotes the multiset containing two occurrences of a, one occurrence of b and three occurrences of c. The fact that, say, a occurs more than once in ⟅a, a, b, c, c, c⟆ is denoted by a ⟅a, a, b, c, c, c⟆. The order in which the occurrences are listed is immaterial; thus, for instance,

⟅a, a, a, b, b, c, c⟆ and ⟅c, a, b, a, c, a, b⟆

denote the same multiset. However, the multiplicity of occurrences of each element is relevant, so

⟅a, a, b, c, c, c⟆ and ⟅a, a, a, b, b, c, c⟆

are two different multisets. The empty multiset is denoted by ∅.

If A and B are multisets, so are A⊎B and A B. An element occurring n times in A and m times in B occurs exactly n+m times in A⊎B and exactly min(n, m) times in A B. It is straightforward to show that: ⊎ and are commutative and associative; ⊎ distributes over ; is idempotent; the absorption law A (A ⊎ B) = A is satisfied; ∅ is an identity for ⊎, whereas it is a zero for .

The set of multisets whose elements are drawn from some set S will be denoted by ℘^{+}(S). Conversely, given a multiset M, the set of all elements which occur in M is denoted by ζ(M). Formally, if M ∈ ℘^{+}(S) then

ζ(M) =^{def} { x ∈ S | x M }.

The set of finite multisets built from the set S is denoted by ℘f^{+}(S). The cardinality of a multiset M is the number of its elements’ occurrences and is denoted by ∥M∥. Thus

∥⟅a, a, b, c, c, c⟆∥ = 6.

Both ℘^{+}(S) and ℘f^{+}(S) are partially ordered with respect to the ordering defined by

A B ⟺^{def} A B = A.

2.4 Cartesian Products and Sequences

By U^{⋆} we will denote the set of all finite sequences of elements drawn from U. The empty sequence is denoted by ε. For x ∈ U^{⋆}, the length of x will be denoted by # x, and, for i = 1, …, # x, the notation x[i] stands for the i-th element of x. The concatenation of sequences x_{1}, x_{2} ∈ U^{⋆} is denoted by x_{1} :: x_{2}.

Let S_{1}, …, S_{n} be sets. We will denote elements of S_{1} ×⋯×S_{n} by (e_{1}, …, e_{n}). The projection mappings π_{i}: S_{1} ×⋯×S_{n} → S_{i} are defined, for i = 1, …, n, by

π_{i}((e_{1}, …, e_{n})) =^{def} e_{i}.

The liftings π_{i}: ℘(S_{1} × ⋯ × S_{n}) → ℘(S_{i}) given by

π_{i}(T) =^{def} { π_{i}(t) | t ∈ T }

will also be used.

2.5 Binary Relations

A set R ⊆ S × S, where S is any set, is called a binary relation (or, simply, relation) over S. Then, for each A ⊆ S, the application of R to A is defined as

R(A) =^{def} { x_{2} ∈ S | ∃x_{1} ∈ A . (x_{1}, x_{2}) ∈ R }.

The inverse of R, denoted R^{−1}, is given by

R^{−1} =^{def} { (x_{2}, x_{1}) ∈ S × S | (x_{1}, x_{2}) ∈ R },

whereas the composition of two relations R_{1}, R_{2} ⊆ S × S is defined much like function’s composition:

\begin{aligned}R_{2} ∘ R_{1} & =^{def} \\ & \{ (x_{1}, x_{3}) ∈ S × S | ∃x_{2} ∈ S . (x_{1}, x_{2}) ∈ R_{1}, (x_{2}, x_{3}) ∈ R_{2} \}. \end{aligned}

Finally, the negation (or complement) of a relation R is simply the corresponding set-theoretic notion:

\overline{R} =^{def} (S × S) \ R.

2.6 Preorders, Partial and Total Orders

A preorder ⪯ over a set P is a binary relation which is reflexive and transitive. For x ∈ P, the downward closure of x, written ↓ x, is defined by

↓ x =^{def} { y ∈ P | y ⪯ x}.

If ⪯ is also antisymmetric, then it is called partial order. ⪯ is a total order if, in addition, for each x, y ∈ P, either x ⪯ y or y ⪯ x. A set P equipped with a partial (resp. total) order ⪯ is said to be partially ordered (resp., totally ordered), and sometimes written ⟨P, ⪯⟩. Partially ordered sets are also called posets. A subset S of a poset ⟨P, ⪯⟩ is said to be a chain if it is totally ordered with respect to ⪯.

Given a poset ⟨P, ⪯⟩ and S ⊆ P, y ∈ P is an upper bound for S if and only if x ⪯ y for each x ∈ S. An upper bound y for S is the least upper bound (or lub) of S if and only if for every upper bound y′ for S it is y ⪯ y′. The lub, when it exists, is unique. In this case we write y = lub S. Lower bounds and greatest lower bounds are defined dually. ⟨P, ⪯⟩ is said bounded if it has a minimum and a maximum element.

2.7 Lattices and Semilattices

A poset ⟨L, ⪯⟩ such that, for each x, y ∈ L, both lub{x, y} and glb{x, y} exist, is called a lattice. In this case, lub and glb are also called, respectively, the join and the meet operations of the lattice. A poset where only the glb operation is well-defined is called a meet-semilattice. A complete lattice is a lattice ⟨L, ⪯⟩ such that every subset of L has both a least upper bound and a greatest lower bound.

An algebra ⟨L, ∧, ∨⟩ is also called a lattice if ∧ and ∨ are two binary operations over L which are commutative, associative, idempotent, and satisfy the following absorption laws, for each x, y ∈ L: x∧(x∨y) = x and x∨(x∧y) = x. The two definitions of lattices are equivalent. This can be seen by setting up the isomorphism given

by: x ⪯ y ⟺^{def} x ∧ y = x ⟺^{def} x ∨ y = y, glb{x, y} =^{def} x ∧ y, and

lub{x, y} =^{def} x ∨ y.

The notions of meet-semilattice and of bounded lattice are imported into the algebraic definition in the natural way. For instance, a bounded lattice [BS81] is an algebra ⟨L, ∧, ∨, ⊥, ⊤⟩ such that ⟨L, ∧, ∨⟩ is a lattice and the following two annihilation laws are satisfied for each x ∈ L: x ∧ ⊥ = ⊥ and x ∨ ⊤ = ⊤. A lattice is said distributive if it satisfies, for each x, y, z ∈ L, the distributive laws x∧(y∨z) = (x∧y)∨(x∧z) and x∨(y∧z) = (x∨y)∧(x∨z). It is well-known that, for lattices, the distributive laws are equivalent.

2.8 Closure and Kernel Operators

A monotone and idempotent self-map ρ: P → P over a poset ⟨P, ⪯⟩ is a kernel operator (or lower closure operator) if it is reductive, that is to say

∀x ∈ P : ρ(x) ⪯ x.

If ρ is extensive, namely

∀x ∈ P : x ⪯ ρ(x),

\underline{2.9. Monoids 31}

then it is called closure operator (or upper closure operator). The reader is referred to [GHK^{+}80] for an extensive treatment of kernel and closure operators.

2.9 Monoids

An algebra ⟨M, ·, 1⟩ is a monoid if and only if ‘·’ is an associative binary operator over M, and 1 ∈ M satisfies the identity law: for each x ∈ M, x · 1 = 1 · x = x. The monoid ⟨M, ·, 1⟩ is said commutative or idempotent if ‘·’ is so.

32  Chapter 2. Preliminaries

A Hierarchy of Constraint Systems

\underline{Contents}

3.1 Introduction . . . . . . . . . . . . . . . . . 34 3.2 A Case Study: CLP . . . . . . . . . . . . . 37

3.2.1 CLP: the Syntax . . . . . . . . . . . . . 37 3.2.2 Non-Standard Semantics for CLP …. 40 3.2.3 Constraint Systems . . . . . . . . . . . . 46 3.2.4 Generalized Semantics . . . . . . . . . . 53 3.2.5 Dealing with Non-Standard Semantics . 57

3.3 Simple Constraint Systems . . . . . . . . . 64 3.3.1 The Atomic Simple Constraint System . 66 3.3.2 A Simple Constraint System for Simple

Types . . . . . . . . . . . . . . . . . . . 67 3.3.3 The Herbrand Simple Constraint System 68 3.3.4 Bounds and Relations Analysis . . . . . 72

3.4 Determinate Constraint Systems . . . . . 75 3.4.1 Definiteness Analysis: Con . . . . . . . 78 3.4.2 The Pattern Domain . . . . . . . . . . . 79 3.5 Powerset Constraint Systems . . . . . . . 80 3.5.1 A Collecting Semantics for Logic Programs 86 3.5.2 Structural Analysis: More than Pattern 86 3.6 Ask-and-Tell Constraint Systems . . . . . 87 3.6.1 Merge Operators . . . . . . . . . . . . . 97 3.6.2 More Bounds and Relations Analysis for

Numeric Domains . . . . . . . . . . . . 109 3.6.3 Definiteness Analysis: Def . . . . . . . 110 3.6.4 Definiteness Analysis: More than Pos . 111 3.7 Combination of Domains . . . . . . . . . . 112 3.7.1 Product Constraint Systems . . . . . . . 113 3.7.2 Approximating Built-ins Behavior … 118 3.8 Conclusion and Future Work . . . . . . . 119

3.1 Introduction

Many interesting and useful data-flow analyses for constraint logicbased languages are aimed at the detection of monotonic properties, that is to say, properties that are preserved as the computation progresses by accumulating constraints along one computation path. Such analyses usually determine the “shape” of the set of solutions of the constraint store at some program points. Analyses that fall in this category include definiteness (groundness), structural information, types closed by instantiation, numerical bounds and relations, symbolic size-relations and so on. The typical examples of non-monotonic properties are freeness and aliasing. Freeness is anti-monotonic: as soon as a variable becomes non-free it will remain such in any stronger constraint store. The aliasing property is subject to a more complex, oscillating evolution: two variables X and Y may not share any other variable at some point, later they can share a common variable Z, eventually Z becomes ground so that X and Y do not share anymore, but suddenly they start sharing another variable W ….

A key observation is that monotonic properties can be conveniently expressed by constraints, which are then accumulated in the analysis process much in the same way as during the “concrete” executions. Thus, frameworks of constraint-based languages are, in principle, general enough to encompass several of their own dataflow analyses. Intuitively, this is done by replacing the standard constraint domain with one that is suitable for expressing the desired information. This fundamental aspect was brought to light, for the case of CLP, by Codognet and Filè [CF92] and elaborated by Giacobazzi, Debray, and Levi [GDL92, GDL95]. Giacobazzi et al. presented a generalized algebraic semantics for constraint logic programs, which is parameterized with respect to an underlying constraint domain. The main advantages of this approach are that:

1. different instances of CLP can be used to define non-standard semantics for constraint logic programs; and

2. several abstract interpretations of CLP programs can be thus formalized inside the CLP paradigm.

In this setting, data-flow analysis is then performed (or at least justified) through abstract interpretation [CC79, CC92a], that is, by “mimicking” the program run-time behavior by “executing” it, in a finite way, on an approximated (abstract) constraint domain.^{1}

By following a generalized semantic approach, the concrete and abstract semantics are more easily related, being instances (over two different constraint systems) of the same generalized semantics, which is entirely parametric on a constraint domain. Thus, to ensure correctness, it will be sufficient to exhibit an “abstraction function” α that is a semi-morphism between the constraint domains [BM83, CC92b].

1Incidentally, here is the reason why we do not like the name ‘static analysis’.

The adjective ‘static’ means “without executing the program”, but we do execute it, though in a non-standard way, over a non-standard domain.

Starting from [GDL95] a more general notion of constraint domain is provided. This allows one to adequately describe both the “logical part” of concrete computations (i.e., answer constraints) and all the monotonic abstract interpretations known to us. In particular our notion of constraint system is able to accommodate approximate inference techniques whose importance relies on very practical considerations, such as representing good compromises between precision and computational efficiency. Some of these techniques, besides being sketched in the examples of this chapter, will be explained in Chapter 5. The new notion of constraint domain requires the introduction of a new generalized semantics framework that is more liberal than the one of [GDL95].

Moreover, and here comes the main point, we show that our constraint domains admit interesting constructions. The most important one consists in upgrading a domain so that it can represent and manipulate dependencies among constraints. This is done by regarding a restricted class of cc agents as constraints [Sar93, SRP91]. This construction, among other things, opens up the possibility of combining domains in a novel and interesting way. By following this methodology, the asynchronous interaction between domains can be expressed with all the elegance that derives from the cc framework.

The plan of the chapter is as follows: Section 3.2 explains our generalized semantics for CLP languages, as well as the abstract interpretation framework we employ. Section 3.3 introduces simple constraint systems: some important building blocks of the hierarchy. Section 3.4 builds on the previous one presenting standard ways of representing and composing finite constraints: determinate constraint systems. In Section 3.5 it is shown how a constraint system is upgraded to incorporate a weak form of disjunction (suitable to monotonic properties) by means of powerset constraint systems. Section 3.6 presents a different kind of upgrade: the one needed in order to have the notion of dependency built into the constraint system. This is done considering ask-and-tell constraint systems. Section 3.7 deals with the interesting problem of combining domains. A technique is shown that consists in applying the ask-and-tell construction to a product constraint system. We feel that, indeed, this is one of the more important contributions of this work. Finally, Section 3.8 draws some conclusions and presents some directions for further study.

3.2 A Case Study: CLP

The constraint domains that are the subject of this work are not bound to a particular class of constraint logic-based languages. However, for the sake of clarity and to help the intuition, we will focus on the class of CLP languages [JL87, JM94], which is more and more influential and captures several existing, implemented languages. We will present a generalized approach to the semantics of CLP programs of which abstract interpretation is an important instance. This is necessary for a full understanding of how the domains of later sections are employed in data-flow analysis of CLP languages.

3.2.1 CLP: the Syntax

Here we give a precise definition of what we will call a CLP(C) program. Notice that here we are concerned with syntax only. In general, C (the language of atomic constraints) is a subset of a first order language L (the language of constraints). Let us start by defining L itself.

Definition 1 (Language of constraints.) Let V and Λ be two disjoint denumerable sets of variable symbols. Let us also fix two particular isomorphisms between Λ and ℕ, and between V and ℕ. Let Vars =^{def} V ∪ Λ. Let Ω and Π_{C} be two finite sets of operation and predicate symbols, respectively, each symbol being characterized with its arity. Let also Vars, Ω and Π_{C} be mutually disjoint. L = L(Vars, Ω, Π_{C}, …) is a language of constraints if and only if it is any first order language with equality built (by means of standard constructions, possibly with connectives and quantifiers) over the given sets of symbols.

(The extra-set of variables Λ allows us to simplify the following treatment. We will stipulate that the heads of clauses can only contain variable symbols drawn from Λ.)

Now, a CLP language can impose restrictions on the form of constraints that may actually appear in programs. However these restrictions must not destroy too much of the language’s expressivity.

Definition 2 (Atomic constraint.) Given a language of constraints L, any subset C of L is a language of atomic constraints if and only if

1. it is closed under variable renaming; and

2. it contains all the equalities ‘X = t’, for each X ∈ Vars and each term t built over Ω and Vars.

Before introducing the full syntax of CLP programs a few remarks about notation are in order. We will denote program variables by means of capital letters (X, Y, …). Tuples of distinct variable will be denoted by X̄, Ȳ, and so forth. Tuples are always assumed to be of the right cardinality, e.g., if p is a predicate symbol of arity n and we write p(X̄), then X̄ is an n-tuple. Special tuples denoted by Λ⃗, denoting initial finite segments of Λ, will also be used (recall that we have fixed a total ordering on Λ). In the above hypotheses, by writing p(Λ⃗) we understand that Λ⃗ denotes the n-tuple consisting of the first n variable symbols in Λ. We will also abuse the notation occasionally by treating a tuple as the set of its components.

We can now introduce the notion of CLP(C) program. Notice that, not to exclude in advance any real CLP language, we carefully avoid making any compromising assumption the could preclude the adoption of particular computation and search rules.

Definition 3 (CLP program.) Let C be a language of atomic constraints with distinguished variable symbols in Λ. Let Π_{P} be a finite set of predicate symbols, disjoint from the symbols used in C. We will denote by A_{P} the set of atoms over Π_{P}, that is

A_{P} =^{def} { q(X̄) | q ∈ Π_{P} , X̄ ∈ Vars^{⋆} }.

A CLP(C) program P over Π_{P} is a finite sequence of rules (or clauses) of the form

p(Λ⃗) :− ⟨b_{1}, …, b_{k}⟩, with p ∈ Π_{P} and k ≥ 0,

where, for 1 ≤ i ≤ k, b_{i} ∈ C ∪ A_{P} and vars(b_{i}) ∩ Λ ⊆ Λ⃗. p(Λ⃗) is called the head of the rule, whereas ⟨b_{1}, …, b_{k}⟩ is the body.

The syntax of any CLP language can be defined in such a way, by augmenting the first order language on which it is based with the set of distinguished variable symbols Λ and transforming each program along the lines of Definition 3 by means of standard techniques. This transformation is always possible by virtue of Definition 2. This normalization of programs has the property that predicate symbols are always applied to tuples of distinct variables. Further, all the heads of the rules defining a program predicate p/n have the same variables in the same positions. Observe that the body B of a clause p(Λ⃗) :− B is a sequence, that is an element of (C ∪ A_{P})^{⋆}. As programs themselves are sequences, the semantic constructions will be free to take into account the selection and search rules used in real languages. So far for the syntax, we now examine the (possibly non-standard) semantics of CLP languages.

3.2.2 Non-Standard Semantics for CLP

Here we start from very basic facts. We recognize the existence of four different activities in the execution, and thus in the analysis, of constraint logic programs:

1. different execution paths are explored;

2. along any path, constraints are accumulated in the so-called constraint store;

3. the constraint store is recursively subdivided into parts. The activity of imposing restrictions in the way different parts can interact is usually called hiding.

4. Pieces of information (parameters) are passed between program rules.

For different (non-standard) semantics, and at different levels of abstraction the degree of correlation among these activities may vary considerably. For instance, while in a “standard” semantic execution paths are explored depending on the satisfiability of the accumulated constraints, in the case of “non-standard” semantic constructions these activities can be somewhat unrelated. Perhaps because the satisfiability check does not make sense in the nonstandard interpretation (think about groundness analysis), or because it can be safely moved outside the construction process.

The way we define domains of interpretation for CLP languages is clearly highly dependent on the application we have in mind. Here we give a general description of what are the characteristics of a quite wide class of domains that covers many non-standard semantics for CLP. Then we will obtain our specific class of domains by imposing restrictions on the general scheme. This is important for a full understanding of the hypotheses that are behind our approach.

Suppose we are interested in some properties of programs’ terminating computations. To this minimal requirement corresponds the following class of interpretation domains.

Definition 4 (Ordered domain.) An ordered domain of interpretation for a CLP language is a structure:

D̄ =^{def} ⟨D, ⊑, ⪯, ⊗, ⊕, ⊥, 1, {¯∃ ; ¯∃X̄}_{X̄∈Vars^{⋆}}, {d_{X̄Ȳ} }_{X̄,Ȳ ∈Vars^{⋆}}⟩,

where

• D is a set of (not better specified) properties,

• ⊑ ⊆ D × D and ⪯ ⊆ D × D are relations over D,

• ⊗: D × D → D and ⊕: D × D → D are binary operators,

• {¯∃ ; ¯∃X̄}_{X̄∈Vars^{⋆}} is a family of unary operators,

• ⊥, 1, and {d_{X̄Ȳ} }_{X̄,Ȳ ∈Vars^{⋆}} are distinguished elements of D.

Furthermore

O_{1}. ⟨D, ⊕, ⊥⟩ is a monoid;

O_{2}. ⟨D, ⊗, 1⟩ is a monoid; O_{3}. ⊥ is an annihilator for ⊗, i.e., for each D ∈ D, ⊥ ⊗ D =

D ⊗ ⊥ = ⊥;

O_{4}. ⟨D, ⊑⟩ is a partial order with minimum element ⊥;

O_{5}. ⟨D, ⪯⟩ is a partial order with maximum element 1.

The ⊕ operator models the merging of information coming from different execution paths. The monoidal structure ensures that the composition process is “insensitive to grouping” and that the empty path (which intuitively corresponds to ⊥) is ignored. Not requiring more than the monoidal structure (e.g., commutativity) means that we have not lost already the ability of taking into account the order in which paths are tried.

The ⊗ operator models the constraint accumulation process. Similar considerations apply here as in the case of ⊕, with the monoidal unit 1 standing, intuitively, for the empty constraint store: the one containing no information at all.

The ¯∃ ; ¯∃X̄ operators represent the hiding process: any variable X ∉ X̄ appearing in the scope of ¯∃ ; ¯∃X̄ is isolated (hidden) from other occurrences of X outside the scope. The “complement sign” that appears on top of ¯∃ ; ¯∃X̄ signifies that we formalize hiding in a dual way with respect to traditional approaches [SRP91, GDL95]. Notice that the exact interpretation of the ¯∃ ; ¯∃X̄ operators remains relatively free. They might stand for projection onto the “variables of interest” X̄ (this will be our case), or renaming to fresh variables (e.g., to model what happens in meta-interpreters: any variable not in X̄ that appears in the scope is given a fresh name), or for very lowlevel operations (e.g., creation and disposal of environment-frames in machine-level traces: one slot for each “local variable”, that is, each X in scope that is not in X̄).

Similar considerations apply also to the distinguished elements representing parameter passing. In our present application the socalled diagonal elements d_{X̄Ȳ} will represent, roughly speaking, the fact that the tuples of variables X̄ and Ȳ are tightly correlated with respect to the properties of interest. When dealing with WAM traces, they could represent, for instance, the multiple assignment of the registers X̄ to the argument registers Ȳ [AK91].

Thus far we have given a mathematical dress to the ingredients that, in our intuition, constitute the computations of CLP programs. We now put ourselves in an abstract interpretation setting.

The non-standard semantics we are interested in will, in general, be captured by some property transformer Φ_{P} : D → D, where P is the program at hand. The purpose of the Φ_{P} operator, at an intuitive level, is that of exploring the (partial) computation paths of P. It will be designed in such a way that iterated applications of Φ_{P} to the “null path” ⊥ corresponds to the iterative re-construction of all the computation paths of P. This exploration process is usually monotonic, in that the “knowledge” about the possible paths increases between successive iterates. This relationship between successive iterates is captured (in a program independent way) by the relation ⊑, which is referred to as the computational order of the interpretation domain. When this is the case, Φ_{P} is conceived so that any of its post-fixpoints (with respect to ⊑) is a property taking into account all the the computational possibilities of P and possibly more.

The relation ⪯, instead, specifies the relative precision of program properties. D_{1} ⪯ D_{2} means that “D_{1} is more precise than D_{2}”. In other words, every set of computations that enjoys property D_{1} enjoys also property D_{2}. In the framework of abstract interpretation, ⪯ is referred to as the approximation ordering of the domain. Notice that the distinction between approximation ordering and computational ordering is important: one has to do with the precision of properties, the other holds between successive iterates of the property transformers. In principle, they could be totally unrelated [CC92b]. (It might be observed that it is not sensible to talk about a computational ordering of the domain without the explicit reference to a particular class of properties’ transformers. However, since the domain of interpretation and the transformers are usually chosen at the same time, we found it more convenient to present the computational ordering as part of the domain.)

The objective of the game is now to derive, for any program P, a property that holds for all its computation paths. Generally, depending on the feasibility of the goal and on various other considerations, this will be given by either the least fixpoint or a post-fixpoint (with respect to ⊑) of Φ_{P} or by some approximations (with respect to ⪯) of them. Observe that the choice of a fixpoint presentation for the non-standard semantics is not restrictive in any way [CC95].

A treatment of abstract interpretation for CLP languages in these very general terms is well outside the scope of this chapter. Thus we start imposing restrictions on the notion of interpretation domain given in Definition 4 so as to obtain a specialized version that is suitable for our purposes. This makes clear what is the class of properties that are captured by the hierarchy of domains that will be presented later.

First of all, we are neither interested in the order in which computations are taken, nor in their multiplicities.^{2} This amounts to

O′_{1}. ⟨D, ⊕, ⊥⟩ is a commutative and idempotent monoid.

Since we are interested in characterizing only the (possibly) successful computations, we disregard (finitely) failed computation paths. We thus postulate the existence in D of a property characterizing failed computations, the fact that extending a failed computation yields a failed computation, and the fact that such computations have to be ignored. More formally, O_{s_{1}}. 0 ∈ D,

2We refer the reader to [LM95] for an understanding of why and how the ordering can be taken into account in the interpretation domain.

O_{s_{2}}. 0 is an annihilator for ⊗,

O_{s_{3}}. 0 is a unit for ⊕.

Then, we focus on monotonic properties, that is, those that are preserved as computation progresses. Since computations progress by adding constraints, this means that

O_{m}. for each D_{1}, D_{2} ∈ D, D_{1} ⊗ D_{2} ⪯ D_{1} and D_{1} ⊗ D_{2} ⪯ D_{2}, must hold.

Further, we restrict our interest to logical properties. This means that ⊗ is interpreted as logical conjunction. Since, by the previous discussion, ⪯ is always interpreted as logical implication, we must have that

O′_{2}. ⟨D, ⊗, 1⟩ is a commutative and idempotent monoid.

O_{l}. for each D_{1}, D_{2} ∈ D, if D_{1} ⪯ D_{2} then D_{1} ⪯ D_{1} ⊗ D_{2}.

Finally, since D_{1} ⊕ D_{2} must hold for all the paths characterized by D_{1} and all the ones characterized by D_{2}, we enforce

O_{p}. D_{1} ⪯ D_{1} ⊕ D_{2} and D_{2} ⪯ D_{1} ⊕ D_{2}, for each D_{1}, D_{2} ∈ D.

These assumptions bring us to interpretation domains that are much simpler than those of Definition 4. By O_{m} and O_{l} we have that D_{1} ⪯ D_{2} holds if and only if D_{1} ⊗D_{2} = D_{1}. Since ⊥ and 0 are both minimal elements with respect to the ordering ⪯ they must coincide. Also, by O_{p} and the previous discussion, the computational ordering ⊑ must be included in ⪯. In fact, in the interpretation domains we will present later, they coincide.

Now the question is: how do we represent the properties of interest? A simple, but far reaching answer was first given in [CF92]: we can represent properties by means of constraints. This opens up the possibility of computing non-standard semantics of CLP, and, in particular, abstract interpretations, within the CLP framework. The result of the abstract interpretation of a CLP program P is obtained by “executing” (in a finite way) another CLP program P′, strongly related to P, over a non-standard domain. Intuitively, this is done by replacing the standard constraint domain with one suitable for expressing the desired information. This possibility led to the idea of a generalized semantics for CLP programs, proposed in [GDL95]. A generalized semantics is parameterized over the (possibly non-standard) constraint system that constitutes the domain of the computation. The advantages of this approach are that:

1. different instances of CLP can be used to define non-standard semantics for constraint logic programs;

2. the semantics of these instances are all captured within a unified algebraic framework; in particular,

3. many relevant abstract interpretations of CLP programs can be formalized inside the CLP paradigm; and

4. it is easier to correlate any two non-standard semantics, when they are instances of the same parametric construction.

The next section is devoted to the class of ordered domains outlined above.

3.2.3 Constraint Systems

Since we aim at a pervasive treatment, we would like to avoid talking too much about what a constraint is. However, we cannot overlook some basic facts on the relationship between constraints and program variables. The purpose of constraints is, roughly speaking, to restrict the range of values variables can take. For our present objectives the following definition suffices.

Definition 5 (Constraint.) Let L be any first-order language with variable symbols in Vars. The class of constraints over L is inductively defined as follows:

1. a well-formed formula of L is a constraint over L;

2. any set of constraints over L is a constraint over L;

3. any (meta-level) predicate μ/n applied to n constraints over L is a constraint over L;

4. nothing is a constraint over L if not by virtue of points 1, 2, or 3.

For a constraint C, it is natural to ask which variables it talks about. We denote this set of variables by FV (C). Notice that FV stands for free variables: this is because there are domains where properties admit bounded occurrences of variables (see Section 3.3.3 on page 68 for an example). Another natural thing is the following: having a constraint C which describes a tuple of variables X̄, we would like to say that this same description applies to a different tuple of variables, Ȳ. We thus introduce the notion of renaming and the notation C[Ȳ /X̄]. More formally, on the relationship between constraints and variables, we can reason inductively as follows.

Definition 6 (All variables, free variables, and renamings.) With reference to Definition 5, if c is a constraint by virtue of point 1 then the notions of variables of c and of free variables of c are assumed as primitive. These sets of variables are denoted, respectively, by vars(c) and FV (c). An invertible mapping from and to variable symbols that is the identity almost everywhere is called renaming. We will use the notation [Ȳ /X̄] for renamings, where Ȳ and X̄ are disjoint tuples of distinct variables. The renaming [Y/X] has no effect on c if X ∉ FV (c), whereas variables’ capture is avoided by consistent renaming of bound variables. Besides that, for each wellformed formula of L, the constraint c[Ȳ /X̄] is assumed as defined.

If C is a constraint because of point 2 then^{3}

vars(C) =^{def} ⋃_{c∈C} vars(c)  (3.1)

and

FV (C) =^{def} { X ∈ Vars | ∃c ∈ C . ∃Y ∈ Vars . c[Y/X] ∉ C }. (3.2)

Notice that the definition of FV (C) implicitly depends on FV (c) for c ∈ C. In fact, if X ∉ FV (c) then c[Y/X] = c and the condition c[Y/X] ∉ C is not satisfied in (3.2) for that particular choice of c and Y. With the definitions given by (3.1) and (3.2) the notion of renaming for C is extended in the expected way. The application of a renaming to C is defined element-wise.

If μ(C_{1}, …, C_{n}) is a constraint by virtue of point 3 then the above notions are extended as they would be in any first-order language, namely, by treating μ as one of the usual logical connectives.

Renamings will always applied carefully so that, when we write C[Ȳ /X̄], it is ensured that FV (C) ∩ Ȳ = ∅. We will emphasize this fact by saying that [Ȳ /X̄] is a renaming for C.

All the members of our hierarchy of domains will turn out to be constraint systems in the precise sense stated by the following definition.

3This definition of FV is an adaptation of the one of dependent variables given by Saraswat [Sar92, Definition 2.3].

Definition 7 (Constraint system.) Any algebra D̄ of the form

⟨D, ⊗, ⊕, 0, 1, {¯∃ ; ¯∃X̄}_{X̄∈Vars^{⋆}}, {d_{X̄Ȳ} }_{X̄,Ȳ ∈Vars^{⋆}}⟩

is a constraint system if and only if it satisfies the following conditions:

G_{0}. D is a set of constraints;

G_{1}. ⟨D, ⊗, 1⟩ is a commutative and idempotent monoid;

G_{2}. ⟨D, ⊕, 0⟩ is a commutative and idempotent monoid;

G_{3}. 0 is an annihilator for ⊗, i.e., for each C ∈ D, C ⊗ 0 = 0;

G_{4}. for each C_{1}, C_{2} ∈ D, the absorption law C_{1} ⊗ (C_{1} ⊕ C_{2}) = C_{1} holds;

G_{5}. for each X̄ ∈ Vars^{⋆} and C ∈ D we have FV (¯∃ ; ¯∃X̄ C) ⊆ X̄.

A constraint system induces the relation ⊢ ⊆ D × D given, for each C_{1}, C_{2} ∈ D, by

C_{1} ⊢ C_{2} ⟺^{def} C_{1} ⊗ C_{2} = C_{1}.  (3.3)

The relation ⊢ is referred to as the approximation ordering of the constraint system. The notation C_{1} ⊩ C_{2} is a convenient shorthand for (C_{1} ⊢ C_{2}) ∧ (C_{1} ≠ C_{2}).

In what follows we will feel free to drop the quantifiers from the notation of the families of projection operators and diagonal elements.

Condition G_{4} can be restated as

C_{1} ⊢ C_{1} ⊕ C_{2} and C_{2} ⊢ C_{1} ⊕ C_{2}.

In this form it clearly stands for the correctness of the merge operation, characterizing it as a (not necessarily least) upper bound operator with respect to the approximation ordering.

Hypothesis 8 Constraint systems must satisfy some other (very technical) conditions related to how they deal with variables. For instance, they do not invent new free variables:

FV (C_{1} ⊗ C_{2}) ⊆ FV (C_{1}) ∪ FV (C_{2}),

and similarly for the merge operator. The operators are also generic in that they are insensible to variable names. This implies that, if [Ȳ /X̄] is a renaming for both C_{1} and C_{2}, then

(C_{1} ⊗ C_{2})[Ȳ /X̄] = C_{1}[Ȳ /X̄] ⊗ C_{2}[Ȳ /X̄].

In particular, if we have also C_{1} ⊢ C_{2}, then C_{1}[Ȳ /X̄] ⊢ C_{2}[Ȳ /X̄]. All these overwhelmingly reasonable requirements will be taken for granted.

Constraint systems enjoy several properties.

Proposition 9 (Properties of c.s.) Any constraint system satisfies the following properties, for each C, C_{1}, C_{2} ∈ D:

1. ⟨D, ⊢, ⊗, 0, 1⟩ is a bounded meet-semilattice;

2. C ⊕ 1 = 1;

3. C_{1} ⊢ C_{1} ⊕ (C_{1} ⊗ C_{2});

4. C_{1} ⊕ C_{2} = C_{2} ⟹ C_{1} ⊢ C_{2}.

Proof

1. First of all, axiom G_{1} implies that ⊢ is a partial order. Reflexivity, transitivity, and antisymmetry of ⊢ come, respectively, from idempotency, associativity, and commutativity of ⊗. The ordering definition (3.3) ensures that ⊗ is the greatest lower bound operator with respect to ⊢. G_{3} and G_{1} imply that 0 and 1 are the minimum and maximum elements with respect to ⊢.

2. Notice that, for each C ∈ D, 1 ⊗ (1 ⊕ C) = 1 is an instance of G_{4}, which, by G_{1} becomes 1 ⊕ C = 1.

3. By G_{4} we have C_{1} ⊗(C_{1} ⊕(C_{1} ⊗C_{2})) = C_{1}, whence the thesis.

4. Suppose C_{1} ⊕ C_{2} = C_{2}. Then G_{4} implies

C_{1} ⊗ C_{2} = C_{1} ⊗ (C_{1} ⊕ C_{2}) = C_{1},

thus C_{1} ⊢ C_{2}. □

Observe that ⟨D, ⊗, ⊕, 0, 1⟩, in general, is not a lattice. Both ⊗ and ⊕ are associative, commutative, and idempotent, but, as stated above, while one of the absorption laws holds (axiom G_{4} of Definition 7), only one direction of the dual law is generally valid (property (3) of Proposition 9). In particular, ⊕ might be not component-wise monotone with respect to ⊢: for each C_{1}, C_{2} ∈ D, from the obvious relations C_{1}⊗C_{2} ⊢ C_{2} and C_{2} ⊢ C_{2} we would get (C_{1}⊗C_{2})⊕C_{2} ⊢ C_{2}. Observe also that ⊕ does not distribute, in general, over ⊗, as this would imply the equivalence of the two absorption laws. It must be stressed that this flexibility of the ⊕ operator is necessary in order to obtain a framework that is general enough to capture several existing analysis domains.

So far for generic constraint systems, we consider now some strengthenings of Definition 7.

Definition 10 (Closed c.s.) A constraint system is said to be closed if and only if

G_{c}. for each family {C_{i} ∈ D}_{i∈ℕ}, the element

⨁_{i∈ℕ}C_{i} =^{def} C_{1} ⊕ C_{2} ⊕ ⋯

exists and is unique in D; moreover, associativity, commutativity, and idempotence of ⊕ apply to denumerable as well as to finite families of operands.

So, the operation of merging together the information coming from all the computation paths always makes sense in a closed constraint system. Notice however that property G_{c} is only necessary when the semantic construction requires it. This will never happen when considering “abstract semantic constructions” formalizing data-flow analyses (which are finite in nature). In these cases the idea of merging infinitely many pieces of information is nonsense in itself. Closedness will instead be required for the constraint systems intended to capture “concrete” program semantics.

Another optional property of constraint systems that we will mention is distributivity.

Definition 11 (Distributive c.s.) Consider the following conditions:

G_{d}. ⟨D, ⊗, ⊕, 0, 1⟩ is a distributive lattice;

G_{D}. for each C ∈ D and each family {C_{i} ∈ D}_{i∈ℕ} such that the element ⨁_{i∈ℕ} C_{i} exists,

C ⊗ (⨁_{i∈ℕ}C_{i}) = ⨁_{i∈ℕ}(C ⊗ C_{i}).

A constraint system is said to be distributive if it satisfies G_{d}. If it satisfies the stronger condition G_{D} then it is called completely distributive.

Distributivity is useful for proving the equivalence of different abstract semantics constructions used for data-flow analysis. Complete distributivity is required for proving that a concrete semantics corresponds to the operational model of the language [GDL95]. Observe that closed and completely distributive constraint systems are instances of the closed semi-rings used in [GDL95].

Definition 12 (Noetherian c.s.) A constraint system is called Noetherian if it satisfies the ascending chain condition:

G_{N}. in D every strictly ascending chain, C_{0} ⊩ C_{1} ⊩ C_{2} ⊩ ⋯, is finite.

For the abstract semantics constructions we will make use of another class of operators over constraints. These operators were introduced in [CC77] and called widenings.

Definition 13 (Widening.) [CC77] Given a constraint system D̄, a binary operator ∇: D → D is called a widening for D̄ if

W_{1}. for each C_{1}, C_{2} ∈ D we have C_{1} ⊢ C_{1} ∇ C_{2} and C_{2} ⊢ C_{1} ∇ C_{2};

W_{2}. for each increasing chain C_{0} ⊢ C_{1} ⊢ C_{2} ⊢ ⋯, the sequence given by C′_{0} =^{def} C_{0} and, for n ≥ 1, C′_{n} =^{def} C′_{n−1} ∇ C_{n}, is stationary after some k ∈ ℕ.

Widenings allow to define convergence acceleration methods that ensure termination of the “abstract interpreter”. However, even when termination is granted anyway (e.g., when the constraint system is Noetherian), these methods are often crucial for achieving rapid termination, that is, for obtaining usable data-flow analyzers. More sophisticated methods for convergence acceleration exist. They employ also narrowing operators and families of widening operators (see [CC92c, CC92b]).

3.2.4 Generalized Semantics

In a generalized semantics setting, the first thing to do is to provide atomic constraints with an interpretation on the chosen constraint system. Suppose that we are interested in deriving information about just two kind of program points: clause’s entries and clause’s successful exits. In a data-flow analysis setting (where this is often the case) that is to say that we want to derive call-patterns and success-patterns. In other words, for each clause we want to derive properties of the constraint store that are valid

• whenever the clause is invoked (call-patterns), or

• whenever a computation starting with the invocation of the clause terminates with success (success-patterns).

Observe that call-patterns depend on the ordering of atoms in the body of clauses and on the selection rule employed. By means of program transformations similar to the magic one [CD93, DR94] we can obtain the call-patterns of the original program (with respect to the selection rule employed) as success-patterns of the transformed one. These transformations, in fact, besides modifying the clauses of the original program, introduce new clauses that characterize the conditions under which the original clauses are invoked. In the transformed program the ordering of atoms in the clause’s bodies is no longer important. Notice that the technique proposed by Debray and Ramakrishnan [DR94], while restricted to logic programs, is more sophisticated than usual transformation approaches, and preserves the connection between call and success patterns. Our generalization of the work of Debray and Ramakrishnan, which is general enough to accommodate the entire CLP framework, will be presented in Chapter 7.

For these reasons we will consider only one kind of program points: clause’s exits. Furthermore, in our domains the operation capturing constraint composition is associative, commutative, and idempotent. This means that we can assume without prejudice that all the clauses are of the form

p(Λ⃗) :− {c_{1}, …, c_{n}} □ {b_{1}, …, b_{k}},

where {c_{1}, …, c_{n}} is a set of atomic constraints, and {b_{1}, …, b_{k}} is a set of atoms. All the other restrictions imposed by Definition 3 must continue to hold. We now must associate a meaning to the finite sets of atomic constraints that occur in clauses.

Definition 14 (Constraint interpretation.) Given a language C of atomic constraints and a domain of interpretation D̄, any computable function [[·]]D̄ ; C : ℘_{f}(C) → D is a constraint interpretation of C in D̄.

Then usually one considers, instead of the syntactic program P, its semantic version over the domain D̄, obtained by interpreting the atomic constraints of clauses through [[·]]D̄ ; C. Recall that we denote by P [r] the r-th clause of P. for r = 1, …, # P.

Definition 15 (Generalized program.) When D̄ is a constraint system, a CLP(D̄) program is a sequence of Horn-like formulas of the form

p(Λ⃗) :− C □ {b_{1}, …, b_{k}},

where C ∈ D̄ is finitely representable. Given a CLP(C) program P and a constraint interpretation [[·]]D̄ ; C, the CLP(D̄) program [[P ]]D̄ ; C is given, for each r = 1, …, # P, by

[[P ]]D̄ ; C[r] ≡ p(Λ⃗) :− [[C]]D̄ ; C □ B ⟺^{def} P [r] ≡ p(Λ⃗) :− C □ B.

An interpretation for a program P over a constraint system D̄ is a function from its program points (one for each clause) to D. Notice that we deviate from standard approaches in that our interpretations associate a meaning to each distinct program rule, and not to each distinct program predicate.

Definition 16 (Interpretation.) Let D̄ be a constraint system, and P a CLP(D̄) program. An interpretation for P over D̄ is any element of

I D̄ ; P =^{def} {1, …, # P } → D.

All the operations and relations over D̄ are extended pointwise to I D̄ ; P. In particular I D̄ ; P is partially ordered by the lifting of ⊢, i.e., for

each I_{1}, I_{2} ∈ I D̄ ; P,

I_{1} ⊢ I_{2} ⟺^{def} ∀r ∈ {1, …, # P } : I_{1}(r) ⊢ I_{2}(r).

Interpretations will be represented by means of function graphs. It is straightforward to show that all the interesting properties of constraint systems lift smoothly to interpretations.

We are left with the choice of the semantics construction, that is, of the “interpretation transformer”. For the purpose of this work we choose a bottom-up construction expressed by a variant of the usual immediate consequence operator T_{P}, taking an interpretation and returning a new interpretation. More specifically, this transformer takes the information arising from a set of computation paths and gives back the information relative to those computation paths that can be obtained by composing in all possible ways, as specified by the program P, the computation paths characterized by the input.

Recall that our set of program variables is Vars = Λ ∪ V, where Λ and V are totally ordered. For Λ⃗ ∈ Λ^{⋆} and W ⊆_{f} Vars, we denote by Ȳ ≪Λ⃗ W the fact that, with respect to the ordering of V, Ȳ is a tuple of distinct consecutive variables in V such that # Ȳ = # Λ⃗ and the first element of Ȳ immediately follows the greatest variable in W.

Definition 17 (Interpretation transformer.) Let D̄ be a constraint system and P be a CLP(D̄) program. The operator induced

by P over I D̄ ; P, T D̄ ; P: I D̄ ; P → I D̄ ; P, is

T D̄ ; P(I) =^{def} { (r, T D̄ ; P(r, I)) | 1 ≤ r ≤ # P },

where T D̄ ; P: {1, …, # P } × I D̄ ; P → D is given by

T D̄ ; P(r, I) =^{def} ⨁ \{ ¯∃ ; ¯∃Λ⃗ C̃ | \begin{matrix}P [r] ≡ p(Λ⃗) :− C \\ □ \{p_{1}(X̄_{1}), …, p_{n}(X̄_{n})\} \\ for each i = 1, …, n: \\ P [r_{i}] ≡ p_{i}(Λ⃗_{i}) :− C_{i} □ B_{r_{i}} \\ Ȳ_{i} ≪Λ⃗_{i} FV (P [r]) ∪ ⋃_{k=1}^{i−1} Ȳ_{k} \\ C̃_{i} = I(r_{i})[Ȳ_{i}/Λ⃗_{i}] \\ C′_{i} = d_{X̄_{i}Ȳ_{i}} ⊗ C̃_{i} \\ C̃ = C ⊗ C′_{1} ⊗ ⋯ ⊗ C′_{n} \end{matrix} \}.

Notice that, in this construction, the merge operator is applied only to finite sets of operands. In summary, once we have fixed the constraint domain D̄ and the interpretation of atomic constraints [[·]]D̄ ; C, the meaning of a CLP(C) program P over D̄ is encoded into the T D̄ ; P operator. Before rushing to require that T D̄ ; P must be continuous on the complete lattice D̄ we better have a closer look to our real needs.

3.2.5 Dealing with Non-Standard Semantics

Given our current focus on data-flow analysis of CLP programs, we consider only the typical case in this field. On one hand we have a “concrete” constraint system D̄^{♮}: it must capture the properties of interest, it must ensure the existence of the least fixpoint of T D̄^{♮} ; P^{♮}, that is, of the meaning of each program P. And, of course, this meaning must correspond to the one obtained by means of the topdown construction representing the operational model of the language (namely, some kind of extended SLD-resolution). This last requirement implies, as shown in [GDL95], that D̄^{♮} must be closed and completely distributive, hence a complete lattice.

On the other hand, in data-flow analysis we have an “abstract” constraint system D̄^{♯}. Here we are much less demanding: we simply want to compute in a finite way an approximation of a post-fixpoint of T D̄^{♯} ; P^{♯} (the least fixpoint might not even exist, or it might be too expensive to compute). And, of course, we need a guarantee that what we compute is a correct approximation of the concrete meaning. Finite computability can be ensured, in general, by using a widening operator.

Thus, in this setting, the concrete and abstract iteration sequences defining, respectively, the concrete meaning and approximations of the abstract meaning of programs are quite different.

Definition 18 (Concrete and abstract iteration sequences.) Consider a closed and completely distributive constraint system D̄^{♮}, and a CLP(D̄^{♮}) program P^{♮}. The concrete iteration sequence for P^{♮} is inductively defined as follows, for all ordinals κ ∈ 𝕆:

T D̄^{♮} ; P^{♮} ↑ 0 =^{def} 0^{♮},
T D̄^{♮} ; P^{♮} ↑ (κ + 1) =^{def} T D̄^{♮} ; P^{♮}(T D̄^{♮} ; P^{♮} ↑ κ),
T D̄^{♮} ; P^{♮} ↑ κ =^{def} ⨁_{β<κ}^{♮}(T D̄^{♮} ; P^{♮} ↑ β),

when κ > 0 is a limit ordinal. (3.4)

Let D̄^{♯} be any constraint system, and let ∇^{♯} be a widening operator over D̄^{♯}. For a CLP(D̄^{♯}) program P^{♯}, the abstract iteration sequence for P^{♯} with widening ∇^{♯} is inductively defined, for k ∈ ℕ, by the recurrence

\{ \begin{matrix}T D̄^{♯} ; P^{♯} ⇑ 0 & =^{def} & 0^{♯}, \\ T D̄^{♯} ; P^{♯} ⇑ (k + 1) & =^{def} & (T D̄^{♯} ; P^{♯} ⇑ k) ∇^{♯} T D̄^{♯} ; P^{♯}(T D̄^{♯} ; P^{♯} ⇑ k). \end{matrix}  (3.5)

Observe that, when D̄^{♯} is Noetherian or when termination can be ensured in other ways, π_{2} (the second projection) can be substituted for ∇^{♯} in (3.5). In these cases, indeed, the restriction of π_{2} to the iterates’ values is a widening operator. In other words, for Noetherian domains, the iteration sequence (eq:concrete-iteration) converges in a finite number of steps and thus can be used for data-flow analysis (despite the fact that it has been termed as “concrete”).

The proof of following result is standard [GDL95].

Theorem 19 If D̄^{♮} is a closed and completely distributive constraint system then, for each CLP(D̄^{♮}) program P^{♮} the least fixpoint of T D̄^{♮} ; P^{♮} exists and is given by lfp(T D̄^{♮} ; P^{♮}) = T D̄^{♮} ; P^{♮} ↑ ω.

This theorem is generalizable to the case where T D̄^{♮} ; P^{♮}, which is naturally partitioned into a sequence of operators (one for each clause), is evaluated component by component à la Gauss-Seidel: at step n+1 the approximation for one clause is updated by evaluating the corresponding component of T D̄^{♮} ; P^{♮} over the current approximations at step n. In the field of numerical analysis very strong hypotheses are required for ensuring the convergence of Gauss-Seidel iteration methods [Bag95b]. Here, instead, the continuity of T D̄^{♮} ; P^{♮} ensures that each chaotic iteration strategy (i.e., such that no component is forgotten indefinitely) based on (3.4) converges to the least fixpoint [Cou78, CC92a].

We now come to the problem of ensuring the correctness of the analysis. We use an abstraction correspondence between the concrete and the abstract constraint systems, which induces an abstraction correspondence between the respective semantics [BM83, CC92b].

Definition 20 (Abstraction function.) Let D̄^{♮} and D̄^{♯} be two constraint systems as in Definition 18. A function α: D^{♮} → D^{♯} is an abstraction function of D̄^{♮} into D̄^{♯} if and only if

A_{1}. α is a semi-morphism, namely, for each C^{♮}, C_{1}^{♮}, C_{2}^{♮} ∈ D^{♮} and

X̄, Ȳ ∈ Vars^{⋆}:
α(C_{1}^{♮} ⊗^{♮} C_{2}^{♮}) ⊢^{♯} α(C_{1}^{♮}) ⊗^{♯} α(C_{2}^{♮}),
α(C_{1}^{♮} ⊕^{♮} C_{2}^{♮}) ⊢^{♯} α(C_{1}^{♮}) ⊕^{♯} α(C_{2}^{♮}),
α(0^{♮}) ⊢^{♯} 0^{♯},
α(¯∃ ; ¯∃♮ ; X̄ C^{♮}) ⊢^{♯} ¯∃ ; ¯∃♯ ; X̄ α(C^{♮}),
α(d_{X̄Ȳ}^{♮} ) ⊢^{♯} d_{X̄Ȳ}^{♯};

A_{2}. for each increasing chain {C_{j}^{♮} ∈ D^{♮}}_{j∈ℕ} and each C^{♯} ∈ D^{♯},

∀j ∈ ℕ : α(C_{j}^{♮}) ⊢^{♯} C^{♯} ⟹ α( \begin{matrix}⨁ & ♮ \\ j∈ℕ \end{matrix} C_{j}^{♮}) ⊢^{♯} C^{♯};

A_{3}. for each C^{♮} ∈ D^{♮} and each renaming [Ȳ /X̄] for C^{♮}, it happens that

α(C^{♮})[Ȳ /X̄] = α(C^{♮}[Ȳ /X̄]).

Any abstraction function α: D^{♮} → D^{♯} is extended pointwise to

α: I D̄^{♮} ; P^{♮} → I D̄^{♯} ; P^{♯}, when # P^{♮} = # P^{♯}.

As anticipated above, one of the beautiful things of the generalized approach is that the abstract meaning of each CLP program can be encoded into another CLP programs. We have thus an abstract compilation approach, where the soundness of the compilation function [[·]]D̄^{♯} ; C is expressed, for CLP(C) programs, by the requirement α∘[[·]]D̄^{♮} ; C ⊢^{♯} [[·]]D̄^{♯} ; C. Here we lift ⊢^{♯} to functions with the meaning that ⊢^{♯} applies pointwise throughout the function domain.

The following result is crucial for proving the correctness of the methodology.

Lemma 21 Consider a CLP(C) program P, two constraint systems D̄^{♮} and D̄^{♯}, the constraint interpretations [[·]]D̄^{♮} ; C and [[·]]D̄^{♯} ; C, the abstraction function α: D^{♮} → D^{♯} such that α∘[[·]]D̄^{♮} ; C ⊢^{♯} [[·]]D̄^{♯} ; C, the concrete program P^{♮} = [[P ]]D̄^{♮} ; C, and the abstract program P^{♯} = [[P ]]D̄^{♯} ; C. Then

∀I^{♮} ∈ I D̄^{♮} ; P^{♮} : ∀I^{♯} ∈ I D̄^{♯} ; P^{♯} : α(I^{♮}) ⊢^{♯} I^{♯} ⟹ α(T D̄^{♮} ; P^{♮}(I^{♮})) ⊢^{♯} T D̄^{♯} ; P^{♯}(I^{♯}).

Proof It is enough to show that, for each r = 1, …, # P we have

∀I^{♮} ∈ I D̄^{♮} ; P^{♮} : ∀I^{♯} ∈ I D̄^{♯} ; P^{♯} :

α(I^{♮}) ⊢^{♯} I^{♯} ⟹ α(T D̄^{♮} ; P^{♮}(r, I^{♮})) ⊢^{♯} T D̄^{♯} ; P^{♯}(r, I^{♯}).

Suppose that I^{♮} and I^{♯} satisfy the hypothesis, i.e., for each clause r′ such that 1 ≤ r′ ≤ # P we have

α(I^{♮}(r′)) ⊢^{♯} I^{♯}(r′).  (3.6)

Then, for

P^{♮}[r] ≡ p(Λ⃗) :− C^{♮} □ {p_{1}(X̄_{1}), …, p_{n}(X̄_{n})},

we have

T D̄^{♮} ; P^{♮}(r, I^{♮}) = ⨁_{j∈J}^{♮} Ĉ_{j}^{♮}

and

T D̄^{♯} ; P^{♯}(r, I^{♯}) = ⨁_{j∈J}^{♯} Ĉ_{j}^{♯},

for some finite set of indices J. Moreover, we can stipulate without prejudice that, for each j ∈ J and each i = 1, …, n, the clause r_{ij} has been used to “resolve” against the i-th atom in the body of r in order to produce both Ĉ_{j}^{♮} and Ĉ_{j}^{♯}. Thus

Ĉ_{j}^{♮} = ¯∃ ; ¯∃♮ ; Λ⃗(C^{♮} ⊗^{♮} \begin{matrix}n \\ ⨂ & ♮ \\ i=1 \end{matrix}(d_{X̄_{i}Ȳ_{i}}^{♮} ⊗^{♮} C̃_{ij}^{♮}))

and

Ĉ_{j}^{♯} = ¯∃ ; ¯∃♯ ; Λ⃗(C^{♯} ⊗^{♯} \begin{matrix}n \\ ⨂ & ♯ \\ i=1 \end{matrix}(d_{X̄_{i}Ȳ_{i}}^{♯} ⊗^{♯} C̃_{ij}^{♯})),

where

C̃_{ij}^{♮} = I^{♮}(r_{ij})[Ȳ_{i}/Λ⃗_{i}]

and

C̃_{ij}^{♯} = I^{♯}(r_{ij})[Ȳ_{i}/Λ⃗_{i}].

By the hypotheses we have α(C^{♮}) ⊢^{♯} C^{♯}, and, using Hypothesis 8, we have also that for each i = 1, …, n and each j ∈ J it is

α(I^{♮}(r_{ij})[Ȳ_{i}/Λ⃗_{i}]) = α(I^{♮}(r_{ij}))[Ȳ_{i}/Λ⃗_{i}]

[by A_{3} of Definition 20]

⊢^{♯} I^{♯}(r_{ij})[Ȳ_{i}/Λ⃗_{i}],

[by (3.6) and Hypothesis 8]

thus α(C̃_{ij}^{♮}) ⊢^{♯} C̃_{ij}^{♯}. Now we can easily conclude:

α(T D̄^{♮} ; P^{♮}(r, I^{♮})) = α( \begin{matrix}⨁ & ♮ \\ j∈J \end{matrix} Ĉ_{j}^{♮})

⊢^{♯} \begin{matrix}⨁ & ♯ \\ j∈J \end{matrix} α(Ĉ_{j}^{♮})

= \begin{matrix}⨁ & ♯ \\ j∈J \end{matrix} α(¯∃ ; ¯∃♮ ; Λ⃗(C^{♮} ⊗^{♮} \begin{matrix}n \\ ⨂ & ♮ \\ i=1 \end{matrix}(d_{X̄_{i}Ȳ_{i}}^{♮} ⊗^{♮} C̃_{ij}^{♮})))

⊢^{♯} \begin{matrix}⨁ & ♯ \\ j∈J \end{matrix} ¯∃ ; ¯∃♯ ; Λ⃗ α(C^{♮} ⊗^{♮} \begin{matrix}n \\ ⨂ & ♮ \\ i=1 \end{matrix}(d_{X̄_{i}Ȳ_{i}}^{♮} ⊗^{♮} C̃_{ij}^{♮}))

⊢^{♯} \begin{matrix}⨁ & ♯ \\ j∈J \end{matrix} ¯∃ ; ¯∃♯ ; Λ⃗(C^{♯} ⊗^{♯} \begin{matrix}n \\ ⨂ & ♯ \\ i=1 \end{matrix}(α(d_{X̄_{i}Ȳ_{i}}^{♮}) ⊗^{♯} α(C̃_{ij}^{♮})))

⊢^{♯} \begin{matrix}⨁ & ♯ \\ j∈J \end{matrix} ¯∃ ; ¯∃♯ ; Λ⃗(C^{♯} ⊗^{♯} \begin{matrix}n \\ ⨂ & ♯ \\ i=1 \end{matrix}(d_{X̄_{i}Ȳ_{i}}^{♯} ⊗^{♯} C̃_{ij}^{♯}))

= T D̄^{♯} ; P^{♯}(r, I^{♯}). □

Theorem 22 Given a CLP(C) program P, two constraint systems D̄^{♮} and D̄^{♯}, the constraint interpretations [[·]]D̄^{♮} ; C and [[·]]D̄^{♯} ; C, the abstraction function α: D^{♮} → D^{♯} such that α ∘ [[·]]D̄^{♮} ; C ⊢^{♯} [[·]]D̄^{♯} ; C, and the programs P^{♮} = [[P ]]D̄^{♮} ; C and P^{♯} = [[P ]]D̄^{♯} ; C, the abstract iteration with widening (3.5) is eventually stable after ℓ ∈ ℕ steps and

α(lfp(T D̄^{♮} ; P^{♮})) = α(T D̄^{♮} ; P^{♮} ↑ ω) ⊢^{♯} (T D̄^{♯} ; P^{♯} ⇑ ℓ).

Proof A straightforward application of a theorem of Cousot and Cousot [CC92b, Proposition 6.20]. Stability of the iteration (3.5) after ℓ ∈ ℕ comes from the properties of widenings. The rest of the proof is carried on using transfinite induction: the base case is given by property A_{1} of abstraction functions, non-limit ordinals are handled through Lemma 21, whereas for limit ordinals property A_{2} is exploited. □

Convergence to a post-fixpoint is still ensured when a chaotic iteration strategy with widening is employed [Cou78].

We now describe a hierarchy of constraint systems that capture most of the analysis domains used for deriving monotonic properties of programs, as well as the “concrete” collecting semantics they abstract.

The basis is constituted by any constraint system that satisfies the conditions of Definition 7. First we show a way (which, of course, is not the only one) of defining such a constraint system. We start from a set of finite constraints, each expressing some partial information about a program execution’s state (i.e., a constraintstore).

3.3 Simple Constraint Systems

A constraint system can be built starting from a set of finite constraints (or tokens), each expressing some partial information. We now define a notion of simple constraint systems (or s.c.s.), very similar to the one introduced in [SRP91], but with a totally uninformative token (⊤) as in [Sco82].

Definition 23 (Simple constraint system.) A structure of the form ⟨C, ⊢, ⊥, ⊤⟩ is a simple constraint system if C is a set of constraints, ⊥, ⊤ ∈ C, and ⊢ ⊆ ℘_{f}(C)×C is an entailment relation such that, for each C, C′ ∈ ℘_{f}(C), each c, c′ ∈ C, and X, Y ∈ Vars:

E_{1}. c ∈ C ⟹ C ⊢ c;

E_{2}. C ⊢ ⊤; E_{3}. (C ⊢ c) ∧ (∀c′ ∈ C : C′ ⊢ c′) ⟹ C′ ⊢ c;

E_{4}. {⊥} ⊢ c;

E_{5}. C ⊢ c ⟹ C[Y/X] ⊢ c[Y/X].

The ‘ ⊢’ symbol is overloaded to denote also the extended relation ⊢ ⊆ ℘(C) × ℘(C) such that, for each C, C′ ∈ ℘(C),

C ⊢ C′ ⟺^{def} ∀c′ ∈ C′ : ∃C′′ ⊆_{f} C . C′′ ⊢ c′.

It is clear that condition E_{1} implies reflexivity of ⊢, while condition E_{3} amounts to transitivity. E_{2} qualifies ⊤ as the least informative token: it will be needed just as a “marker” when the product of simple constraint systems will be considered (see Section 3.7 and [Sco82]). E_{4} ensures that C is a finitely generable element (see Definition 25). Condition E_{5}, referred to as genericity, states that the entailment is insensible to variables’ names.^{4}

By axioms E_{1} and E_{3} of Definition 23 the entailment relation of a simple constraint system is a preorder. Now, instead of considering the quotient poset with respect to the induced equivalence relation, a particular choice of the equivalence classes’ representatives is made: closed sets with respect to entailment. This representation is a very convenient domain-independent strong normal form for constraints.

Definition 24 (Elements.) [SRP91] The elements of an s.c.s. ⟨C, ⊢, ⊥, ⊤⟩ are the entailment-closed subsets of C, namely those C ⊆ C such that, whenever ∃C′ ⊆_{f} C . C′ ⊢ c, then c ∈ C. The set of elements of ⟨C, ⊢, ⊥, ⊤⟩ is denoted by |C|.

4In [Sar92] a stronger notion of genericity is used, namely C[t/X] ⊢ c[t/X] whenever C ⊢ c, for any term t. This is too strong for our purposes. For instance, it would force us to treat non-linear numeric constraints in the same way as linear ones in CLP(R). See Chapter 5 on this subject.

The poset of elements is thus given by ⟨|C|, ⊇⟩. Notice that we deviate from [SRP91] in that we order our constraint systems in the dual way, as is customary in abstract interpretation.

Definition 25 (Inference map, finite elements.) Given a simple constraint system ⟨C, ⊢, ⊥, ⊤⟩, the inference map of ⟨C, ⊢, ⊥, ⊤⟩ is ρ: ℘(C) → ℘(C) given, for each C ⊆ C, by

ρ(C) =^{def} { c | ∃C′ ⊆_{f} C . C′ ⊢ c }.

It is well-known that ρ is a kernel operator, over the complete lattice ⟨℘(C), ⊇⟩, whose image is |C|. The image of the restriction of ρ onto ℘_{f}(C) is denoted by |C|_{0}. Elements of |C|_{0} are called finitely generated constraints or simply finite constraints.

From here on we will work only with finitely generated constraints, since we are not concerned with infinite behavior of CLP programs.

In general, describing the “standard” semantics of a CLP(X) language is done as follows. Let T be the theory that corresponds to the domain X [JL87]. Let D be an appropriate set of formulas in the vocabulary of T closed under conjunction and existential quantification. Define Γ ⊢ c if and only if Γ entails c in the logic, with non-logical axioms T. Then (D, ⊢) is the required simple constraint system. For CLP(H) (a.k.a. pure Prolog) one takes the Clark’s theory of equality (see Section 3.3.3). For CLP(ℝ) the theory RCF of real closed fields would do the job.^{5} We see now some simple constraint systems.

3.3.1 The Atomic Simple Constraint System

This is probably the simplest useful example of s.c.s. The tokens include variable names. A variable name, when present in a constraint, expresses the fact that the variable has some (unspecified) property. For instance, being definitely bound to a ground value. In this case, X is just a shorthand for ground(X). This s.c.s. is thus given by C =^{def} Vars ∪ {⊥, ⊤} and by the smallest relation ⊢ ⊆ ℘_{f}(C) × C satisfying conditions E_{1}–E_{5} of Definition 23. We will refer to this structure as the atomic s.c.s.

5Beware not to confuse CLP(ℝ), the idealized language over the reals [JM94], with CLP(R), the (far from ideal) implemented language and system [JMSY92b].

A useful extension is to include tokens involving two variable names. These tokens state that the two variables involved share the property of interest: one enjoys it if and only if the other one does. More formally, we have

C′ =^{def} C ∪ { X ⇋ Y | X, Y ∈ Vars },

and the entailment relation is suitably extended to C′ requiring, for each X, Y, Z ∈ Vars:

{X ⇋ Y } ⊢ Y ⇋ X;
{X ⇋ Y, Y ⇋ Z} ⊢ X ⇋ Z;
{X, X ⇋ Y } ⊢ Y.

3.3.2 A Simple Constraint System for Simple Types

A slightly more interesting example is when we have more than one monadic (constraint) predicate. This is the case of domains of simple types where tokens like number(X) or atom(X) indicate that the variable X is bound to take numerical values, or Herbrand constants, respectively. The reader can easily figure out how to define such a simple constraint system. Figure 3.1 suggests, by means of a Hasse diagram, a possible set of tokens together with its entailment relation.

[figure: ⊤; number; symbolic; integer; atom; compound; ⊥]

Figure 3.1: A lattice of simple types.

3.3.3 The Herbrand Simple Constraint System

Since almost any constraint logic-based language is an extension of Prolog, the Herbrand (or finite trees) constraint system plays a fundamental role in the field. If taken seriously, this is a quite complicated constraint system. This is due to the fact that Herbrand constraints in themselves are quantified formulas, which is not the case for most other constraint languages. For our purposes we need a clear separation between “real” program variables (in unification theory called eliminable variables) from quantified variables appearing only in the constraints (called parameters). We also wish to avoid having to talk modulo renaming. For these reasons^{6} here we give a quite detailed account of a simple constraint system capturing Herbrand constraints.

6And also because we were not able to find in the literature a precise description (in terms of simple constraint systems) to refer the reader to.

Basically, the tokens are systems of equations in solved form (see [LMM88]). Besides that, they are also minimal (i.e. they do not contain useless equations, and the scope of parameters is as tight as possible), and canonical (i.e. the parameters’ names are selected in a unique way). Formally, the set of tokens, E, contains the symbol ⊥ and all the syntactic objects of the form

λ⟨Y_{1}, …, Y_{p}⟩ ▪ ⟨X_{1} = t_{1}, …, X_{n} = t_{n}⟩

such that

1. {X_{1} = t_{1}, …, X_{n} = t_{n}} is in solved form (i.e. X_{1}, …, X_{n} are all distinct and do not occur on right-hand sides) with parameters {Y_{1}, …, Y_{p}} = ⋃_{i=1}^{n} vars(t_{i}).

2. Equations of the form X_{i} = Y_{j} where Y_{j} does not occur elsewhere are useless and thus forbidden: for each i ∈ {1, …, n}, if t_{i} ≡ Y_{j}, then there exists k ∈ {1, …, n} with k ≠ i such that Y_{j} ∈ vars(t_{k}).

3. The scope of each parameter cannot be restricted to a proper subset of the equations at hand: the graph where {1, …, n} are the nodes and the edges are

{ (i, j) | i, j = 1, …, n and vars(t_{i}) ∩ vars(t_{j}) ≠ ∅ }

is strongly connected.

4. The parameters’ names are completely determined by the equations. With respect to the total ordering of variables we have^{7}: a. X_{1} < X_{2} < ⋯ < X_{n} ≺ Y_{1} ≺ Y_{2} ≺ ⋯ ≺ Y_{p}. This

7In this context, X ≺ Y means that Y immediately follows X in the order.

identifies the variable symbols to be used. b. They are assigned on a first-seen basis traversing the right sides of the equations in some standard order: if ⟨Y_{i_{1}}, …, Y_{i_{l}}⟩ is the sequence of variables encountered in a depth-first, left-to-right traversal of ⟨t_{1}, …, t_{n}⟩, then^{8} for each h, k ∈ {1, …, l} with h < k

\begin{matrix}∀h′ < h, h′ ≥ 1 : Y_{i_{h′}} ≠ Y_{i_{h}} \\ ∀k′ < k, k′ ≥ 1 : Y_{i_{k′}} ≠ Y_{i_{k}} \end{matrix} \} ⟹ Y_{i_{h}} < Y_{i_{k}}.

Notice that, by selecting the parameter’s names in a unique way, we will avoid all the burden of talking “modulo renaming”. Tokens of the form λε ▪ Ē can be simply denoted by Ē. The token λε ▪ ε is denoted by ⊤.

The entailment relation ⊢ ⊆ ℘_{f}(E) × E is defined by suitably augmenting the conditions of Definition 23. First we must say how we combine tokens to get other tokens:

{c_{1}, c_{2}} ⊢ c, if c ∈ σ(c_{1}, c_{2}),  (3.7)

where σ: E × E → ℘_{f}(E) is computed as follows.

1. If c_{1} ≡ ⊥ or c_{2} ≡ ⊥ then return {⊥}. Otherwise, assume that c_{1} ≡ λȲ_{1} ▪ Ē_{1} and c_{2} ≡ λȲ_{2} ▪ Ē_{2}.

2. Consistently rename the parameters Ȳ_{2} of c_{2} apart from Ē_{1}. Let λȲ′_{2} ▪ Ē′_{2} be the result.

3. Compute the solved form of Ē_{1} ∪ Ē′_{2} with parameters Ȳ_{1} ∪ Ȳ′_{2}. If the system is unsolvable return {⊥}. If it is solvable, let F̄ = {X_{1} = t_{1}, …, X_{m} = t_{m}} be the solved form obtained.

8Notice that we treat ⟨t_{1}, …, t_{n}⟩ as a special term (or tree) with a fictitious root added.

4. Define the graph

G =^{def} ⟨F̄, { (e_{i}, e_{j}) | e_{i}, e_{j} ∈ F̄ and vars(e_{i}) ∩ vars(e_{j}) ≠ ∅ }⟩

and let {F̄_{1}, …, F̄_{s}} be the nodes of the condensed graph G^{∗} (i.e., the strongly connected components of G).

5. From each F̄_{i} obtain the token λZ̄_{i} ▪ H̄_{i} (by ordering the equations, suitably renaming the parameters and so on).

6. Return { λZ̄_{i} ▪ H̄_{i} | i = 1, …, s }.

Now the entailment must be closed by anti-instance, i.e.

{c′} ⊢ c, if c′ ⊢ c,  (3.8)

where ⊢ ⊆ E × E is such that

λȲ_{1} ▪ ⟨X_{1} = t_{1}, …, X_{n} = t_{n}⟩ ⊢ λȲ_{2} ▪ ⟨Z_{1} = s_{1}, …, Z_{m} = s_{m}⟩

if and only if {Z_{1}, …, Z_{m}} ⊆ {X_{1}, …, X_{n}} and there exists a substitution θ such that^{9}

Z_{1} ≡ X_{i_{1}}, …, Z_{m} ≡ X_{i_{m}} and ⟨t_{i_{1}}, …, t_{i_{m}}⟩ ≡ ⟨s_{1}, …, s_{m}⟩θ.

The effect of a renaming [W̄ /V̄] onto a token λȲ ▪ Ē is to substitute the non-parameters in V̄ with the correspondent ones in W̄ (notice that, by definition, (W̄ ∪ Ȳ) ∩ FV (λȲ ▪ Ē) = ∅), and then to rename the parameters Ȳ (there is a unique way to do that) so to ensure that the result is still a token.

The definition is completed by saying that ⊢ ⊆ ℘_{f}(E) × E is the smallest relation satisfying (3.7), (3.8), and conditions E_{1}–E_{5} of Definition 23. All these complications have the great advantage of being confined here. We will see, in fact, that with this definition equivalence of constraints will amount to the equality of the correspondent set of tokens. Similarly, extracting the common information of two Herbrand constraints will be done by taking the intersection of their sets of tokens.

9Again, we regard ⟨ … ⟩ as a special function symbol.

3.3.4 Bounds and Relations Analysis

The analysis described in Chapter 5 is based on constraint inference, a variant of constraint propagation [Dav87]. This technique, developed in the field of Artificial Intelligence, has been applied to temporal and spatial reasoning [All83, Sim83, Sim86].

Let us focus our attention on arithmetic domains, where usually the constraints are binary relations over expressions. Let E be the set of arithmetic expressions of interest. Consider also a family I of subsets of ℝ closed under intersection, that is, for each I_{1}, I_{2} ∈ I we also have I_{1} ∩ I_{2} ∈ I. The set of arithmetic relationships is

R =^{def} {=, ≠, ≤ <, ≥, >}

and our constraints are given by

C =^{def} { e_{1} ▷◁ e_{2} | e_{1}, e_{2} ∈ E, ▷◁ ∈ R }
∪ { e ◁ I | e ∈ E, I ∈ I } ∪ {⊥, ⊤}.

The meaning of the constraint e ◁ I is the obvious one: any real value the expression e can take is contained in I. Thus C provides a mixture of qualitative (relationships between expressions) and quantitative knowledge (bounds on the values of the expressions).

The approximate inference techniques we are interested in can be encoded into an entailment relation ‘ ⊢’ over C. First we need to specify how we deal with intervals: we can intersect them, weaken them, and we detect failure by recognizing the empty ones:

{e ◁ I_{1}, e ◁ I_{2}} ⊢ e ◁ I_{1} ∩ I_{2},
{e ◁ I} ⊢ e ◁ I′,  if I ⊆ I′,
{e ◁ I} ⊢ ⊥,  if I = ∅.

Two techniques for exploiting pure qualitative information are symmetric and transitive closure:

\begin{aligned}\{e_{1} ▷◁ e_{2}\} & ⊢ & e_{2} ▷◁^{−1} e_{1}, \\ \{e_{1} ▷◁ e_{2}, e_{2} ▷◁′ e_{3}\} & ⊢ & e_{1} ▷◁′′ e_{3}, & if ▷◁′′ = tc(▷◁, ▷◁′), \end{aligned}

where ▷◁^{−1} is the inverse of ▷◁ (e.g., < is the inverse of >, ≥ of ≤ and so on), and tc : R × R ↣ R is the partial function individuated by the following table:

tc < ≤ > ≥ = ≠
< < <  <
≤ < ≤  ≤
>  > > >
≥  > ≥ ≥
= < ≤ > ≥ = ≠
≠  ≠

This technique allows the inference of A < C from A ≤ B and B < C. Of course, qualitative information can be combined and can lead to the detection of inconsistencies:

{e_{1} ▷◁ e_{2}, e_{1} ▷◁′ e_{2}} ⊢ e_{1} ▷◁′′ e_{2},

if ∀x, y ∈ ℝ : (x ▷◁ y ∧ x ▷◁′ y) ⇒ x ▷◁′′ y, and

{e_{1} ▷◁ e_{2}, e_{1} ▷◁′ e_{2}} ⊢ ⊥,

whenever ∀x, y ∈ ℝ : ¬(x ▷◁ y ∧ x ▷◁′ y).

A classical quantitative technique is interval arithmetic: it allows to infer the variation interval of an expression from the intervals of its sub-expressions. Let f(e_{1}, …, e_{k}) be any arithmetic expression having e_{1}, …, e_{k} as subexpressions. Then

{f(e_{1}, …, e_{k}) ◁ I, e_{1} ◁ I_{1}, …, e_{k} ◁ I_{k}}

⊢ f(e_{1}, …, e_{k}) ◁ f̈(I_{1}, …, I_{k}),

where f̈: I^{k} → I is such that for each x_{1} ∈ I_{1}, …, x_{k} ∈ I_{k}, it happens that f(x_{1}, …, x_{k}) ∈ f̈(I_{1}, …, I_{k}). For example,

A ◁ [ 3, 6) ∧ B ◁ [ −1, 5 ] ⊢ A + B ◁ [ 2, 11).

Another technique is numeric constraint propagation, which consists in determining the relationship between two expressions when their associated intervals do not overlap, except possibly at their endpoints. The associated family of axioms is

{e_{1} ◁ I_{1}, e_{2} ◁ I_{2}} ⊢ e_{1} ▷◁ e_{2}, if ∀x_{1} ∈ I_{1}, x_{2} ∈ I_{2} : x_{1} ▷◁ x_{2}.

For example, if A ∈ (−∞, 2 ], B ∈ [ 2, +∞), and C ∈ [ 5, 10 ], we can infer that A ≤ B and A < C. It is also possible to go the other way around, i.e., knowing that U < V may allow to refine the intervals associated to U and V so that they do not overlap. We call this weak interval refinement:

{e_{1} ▷◁ e_{2}, e_{1} ◁ I_{1}, e_{2} ◁ I_{2}} ⊢ e_{1} ◁ I′_{1},

where I′_{1} =^{def} { x_{1} ∈ I_{1} | ∃x_{2} ∈ I_{2} . x_{1} ▷◁ x_{2} }. This is an example of local-consistency technique [Mon74, Mac77, Fre78]. In summary, by considering the transitive closure of ⊢ and with some minor technical additions we end up with a simple constraint system that characterizes precisely the combination of the above techniques. Other techniques can be easily incorporated (see Chapter 5).

What we have just presented is a watered-down version of the numerical component (presented as a simple constraint system) employed in the China analyzer (see Chapter 5 for the details). Now we are in position to introduce an important class of members of the hierarchy.

3.4 Determinate Constraint Systems

Determinate constraint systems are at the bottom of the hierarchy. Such a construction is uniquely determined by a simple constraint system together with appropriate merge operator and diagonal elements. Notice that, for simplicity, we present only the finite fragment of the constraint system, that is, the sub-structure consisting of the finite elements only.

Definition 26 (Determinate constraint system.) Consider a simple constraint system, S =^{def} ⟨C, ⊢, ⊥, ⊤⟩. Let 0, 1 ∈ |C|_{0} and ⊗: |C|_{0} × |C|_{0} → |C|_{0} be given, for each C_{1}, C_{2} ∈ |C|_{0}, by

0 =^{def} C,
1 =^{def} ρ(∅),
C_{1} ⊗ C_{2} =^{def} ρ(C_{1} ∪ C_{2}).

Let ⊕: |C|_{0} × |C|_{0} → |C|_{0} be an operator satisfying conditions G_{2} and G_{4} of Definition 7. The projection operators ¯∃ ; ¯∃X̄: |C|_{0} → |C|_{0} are given, for each X̄ ∈ Vars^{⋆} and each C ∈ |C|_{0}, by

¯∃ ; ¯∃X̄ C =^{def} ρ({ c ∈ C | FV (c) ⊆ X̄ }).

Finally, let {d_{X̄Ȳ} }_{X̄,Ȳ ∈Vars^{⋆}} be a family of elements of |C|_{0}. We will call the structure ⟨|C|_{0}, ⊗, ⊕, 0, 1, {¯∃ ; ¯∃X̄}, {d_{X̄Ȳ} }⟩ a determinate constraint system over S and ‘ ⊕’.

The following is an adaptation to our more relaxed hypotheses of a result in [Sar92, Proposition 2.3].

Lemma 27 Let ⟨C, ⊢, ⊥, ⊤⟩ be a simple constraint system. For each C ⊆ C we have FV (ρ(C)) ⊆ FV (C).

Proof Suppose X ∈ FV (ρ(C)). By Equation (3.2) of Definition 6 there exist c ∈ ρ(C) and Z ∈ Vars such that c[Z/X] ∉ ρ(C). This implies that there exists C′ ⊆_{f} C such that C′ ⊢ c (otherwise c ∉ ρ(C)). Now suppose, towards a contradiction, that X ∉ FV (C). This means that for each c′ ∈ C′ and each Y ∈ Vars we have c′[Y/X] ∈ C, and thus C′[Y/X] ⊆_{f} C. By the genericity axiom E_{5} of Definition 23, this implies that for each Y ∈ Vars we have C′[Y/X] ⊢ c[Y/X], and thus that c[Y/X] ∈ ρ(C), contradicting the hypothesis X ∈ FV (ρ(C)). □

Theorem 28 Each determinate constraint system is indeed a constraint system. Moreover, for each C_{1}, C_{2} ∈ |C|_{0}, we have C_{1} ⊢ C_{2} if and only if C_{1} ⊇ C_{2}.

Proof With reference to Definition 7, G_{0} is true because of point (2) in Definition 5. G_{1} clearly holds by definition of ⊗ and ρ. G_{2} and G_{4} are enforced by the choice of the merge operator. For G_{3}, if C ∈ |C|_{0} then C ⊗ 0 = ρ(C ∪ C) = ρ(C) = 0. Then, by definition of ⊢, C_{1} ⊢ C_{2} if and only if ρ(C_{1} ∪ C_{2}) = C_{1}. Thus, if C_{1} ⊢ C_{2} we have, by ρ extensivity, C_{1} ∪ C_{2} ⊆ ρ(C_{1} ∪ C_{2}) = C_{1}, which implies C_{1} ⊇ C_{2}. Conversely, assuming C_{1} ⊇ C_{2}, ρ idempotence gives ρ(C_{1} ∪ C_{2}) = ρ(C_{1}) = C_{1}. Finally, G_{5} is an immediate consequence of the definition of ¯∃ ; ¯∃X̄ and of Lemma 27. □

The choice of a suitable merge operator, required in addition to an s.c.s. to obtain a determinate constraint system, can be done with relative freedom. This freedom can often be conveniently exploited in order to get a reasonable complexity/precision tradeoff. The same will apply to the ask-and-tell constraint systems of Section 3.6.

Observe that, given C_{1}, C_{2} ∈ |C|_{0}, there is no a priori guarantee that C = C_{1} ∩ C_{2} ∈ |C|_{0}. In fact, there are simple constraint systems where this is false^{10}. However, since ρ is an algebraic closure operator by its very definition, if it turns out that C ∈ |C|_{0}, then there exists C_{0} ⊆_{f} C such that ρ(C_{0}) = C [BS81]. That said, defining the merge operator as set intersection works in many cases.

A trivial example of merge operator is the following, whose definition is independent from the simple constraint system at hand:

C_{1} ⊕ C_{2} =^{def} \{ \begin{matrix}C_{1}, & if C_{1} = C_{2} or C_{2} = 0; \\ C_{2}, & if C_{1} = 0; \\ 1, & otherwise. \end{matrix}  (3.9)

For a less trivial example, suppose we are approximating subsets of ℝ^{n} by means of (closed) convex polyhedra. Of course they will be represented by sets of linear disequations over x_{1}, …, x_{n}, but, for the purpose of the present example, we will consider the polyhedra themselves. For any convex polyhedra X, Y ⊆ ℝ^{n}, define X ⊢ Y if and only if X ⊆ Y and

X ⊕ Y =^{def} \{ \begin{matrix}X, & if X = Y or Y = ∅; \\ Y, & if X = ∅; \\ bb(X ∪ Y), & otherwise; \end{matrix}  (3.10)

10For the interested reader: consider the set of tokens {⊥, a, b, ⊤} ∪ {t_{i}}_{i∈ℕ}.

Choose the least entailment relation such that: {t_{i}} ⊢ t_{j} if and only if i ≥ j, {a} ⊢ {t_{i}}_{i∈ℕ}, {b} ⊢ {t_{i}}_{i∈ℕ}, and {a, b} ⊢ ⊥. It is easy to see that the intersection of the finite elements generated by {a} and {b}, respectively, is {t_{i}}_{i∈ℕ}, which is not finite.

where bb(Z) is the smallest “bounding box” containing Z ⊆ ℝ^{n}, that is,

bb(Z) =^{def} \{ (x_{1}, …, x_{n}) | \begin{matrix}∀i = 1, …, n : \\ inf π_{i}(Z) ≤ x_{i} ≤ sup π_{i}(Z) \end{matrix} \}.

The most precise merge operator is, of course, given by the convex hull, namely,

X ⊕ Y =^{def} min{ W ⊆ ℝ^{n} | W ⊇ X ∪ Y and W is a c.p. }. (3.11)

Notice that (3.11) satisfies both the absorption laws (thus giving rise to a lattice), (3.9) and (3.10) do not. None of them results in a distributive constraint system. Furthermore, (3.9) and (3.10) are closed, while (3.11) is not.

3.4.1 Definiteness Analysis: Con

Consider the extension of the atomic simple constraint system, C′, introduced in Section 3.3.1, and apply to it the determinate constraint system construction with

C_{1} ⊕ C_{2} =^{def} C_{1} ∩ C_{2}, for each C_{1}, C_{2} ∈ |C′|.

Let also the diagonal elements be given, for each X̄, Ȳ ∈ Vars^{⋆} of the same cardinality, by

d_{X̄Ȳ} =^{def} ρ({ π_{i}(X̄) ⇋ π_{i}(Ȳ) | 1 ≤ i ≤ # X̄ }).

The resulting domain (a closed and Noetherian d.c.s.) is the simplest one for definiteness analysis, and it was used in early groundness analyzers [Mel85, JS87]. The name Con comes from the fact that elements of the form {X_{1}, …, X_{n}} are usually regarded as the conjunction X_{1} ∧ ⋯ ∧ X_{n}, meaning that X_{1}, …, X_{n} are definitely bound to a unique value. In this view, ⊗ clearly amounts to logical conjunction.

Con is a very weak domain for definiteness analysis. It cannot capture either “aliasing” (apart from the special kind of aliasing arising from parameter passing) or more complex dependencies between variables such as those implied by “concrete” constraints like A = f(B, C) and A + B + C = 0. Moreover it cannot represent or exploit disjunctive information.

3.4.2 The Pattern Domain

Endowing the Herbrand simple constraint system of Section 3.3.3 with the merge operator

C_{1} ⊕ C_{2} =^{def} C_{1} ∩ C_{2}, for each C_{1}, C_{2} ∈ |E|_{0}.

and the diagonals (we assume for simplicity that π_{i}(X̄) ≤ π_{i}(Ȳ), for i = 1, …, n)

d_{X̄Ȳ} =^{def} ρ ( \{ λ⟨Z_{i}⟩ ▪ ⟨π_{i}(X̄) = Z_{i}, π_{i}(Ȳ) = Z_{i}⟩ | \begin{matrix}1 ≤ i ≤ # X̄ \\ π_{i}(Ȳ) ≺ Z_{i} \end{matrix} \})

yields a closed and Noetherian d.c.s. suitable for structural analysis. Observe that all the finitely generable elements in |E|_{0} \ 0 are finite, since there is at most one normalized greatest common instance for each finite set of terms (axiom (3.7) of the entailment relation) and only a finite number of (normalized) anti-instances for any given set of terms (axiom (3.8)) [LMM88]. As the ordering is reverse set inclusion, Noetherianity clearly follows.

Since the tokens are normalized and elements are closed by antiinstance, merging two elements of |E|_{0} makes implicit the fact that to each eliminable variable in the result will be assigned the least common anti-instance (lca) of the terms assigned in the elements to be merged (and all its further anti-instances). In a real implementation, where closure by anti-instance is obviously not performed, this will correspond to the use of some anti-unification algorithm to compute the lca of terms assigned to the same eliminable variable.

The Pattern domain presented here, which is a reformulation of the ones defined in [Mus90, LCVH92], deals only with pure structural information. In [CLV94] a much more powerful domain is presented, Pat(ℜ), parametric with respect to any abstract domain ℜ in their framework (which is not restricted to monotonic properties). It is possible to modify the Pattern domain presented in this section so to make it parametric with respect to any constraint system D̄, still obtaining a constraint system. In this way one can achieve the same results of the parametric domain of [CLV94], though in the restricted context of monotonic properties. Of course, the backside of this restriction is a greater simplicity in the definition of the parametric domain Pattern(D̄).

Now that we have seen how many constraint systems can be built, we will show what the induced members of the hierarchy look like.

3.5 Powerset Constraint Systems

For the purpose of program analysis of monotonic properties it is not necessary to represent the “real disjunction” of constraints collected through different computation paths, since we are interested in the common information only. To this end, a weaker notion of disjunction suffices.

We define powerset constraint systems, which are instances of a well-known construction: disjunctive completion [CC92b]. For doing that we need some notions from the theory of posets.

Given a poset ⟨L, ⊥, ≤⟩, the relation ⪯ ⊆ ℘(L) × ℘(L) induced by ≤ is given, for each S_{1}, S_{2} ∈ ℘(L) by

(S_{1} ⪯ S_{2}) ⟺^{def} (∀x_{1} ∈ S_{1} : ∃x_{2} ∈ S_{2} . x_{1} ≤ x_{2}).  (3.12)

A subset S ∈ ℘(L) is called non-redundant if and only if ⊥ ∉ S and

∀x_{1}, x_{2} ∈ S : x_{1} ≤ x_{2} ⟹ x_{1} = x_{2}.  (3.13)

The set of non-redundant subsets of L with respect to ≤ is denoted by ℘_{n}(L, ≤). The function Ω_{L}^{≤}: ℘(L) → ℘_{n}(L, ≤), mapping each set into its non-redundant counterpart is given, for each S ∈ ℘(L), by

Ω_{L}^{≤}(S) =^{def} S \ { x ∈ S | x = ⊥ ∨ ∃x′ ∈ S . x < x′ }.  (3.14)

Thus, for S ∈ ℘(L), Ω_{L}^{≤}(S) is the set of maximal elements of S. However, there is no guarantee, in general, that such maximal elements exist: L could be an infinite chain without an upper bound in L, and thus would be mapped to ∅ by Ω_{L}^{≤}. We will denote by ℘_{c}(L) the set of all those S ∈ ℘(L) such that, if S contains an infinite chain C, then it also contains an upper bound for C. Observe that ℘_{f}(L) ⊆ ℘_{c}(L) and that, if L satisfies the ascending chain condition, then ℘(L) = ℘_{c}(L).

Proposition 29 If ⟨L, ⊥, ≤⟩ is a poset the following hold:

1. ⟨℘_{n}(L, ≤), ⪯⟩ is a poset;

2. for each S ∈ ℘_{c}(L), we have both Ω_{L}^{≤}(S) ⪯ S and S ⪯ Ω_{L}^{≤}(S);

3. for each family {S_{i} ∈ ℘(L)}_{i∈I} we have

Ω_{L}^{≤}(⋃_{i∈I} Ω_{L}^{≤}(S_{i})) = Ω_{L}^{≤}(⋃_{i∈I} S_{i});

4. if ⟨L, ⊥, ≤, ∧⟩ is a meet-semilattice then for each x ∈ L and each S ∈ ℘_{c}(L)

Ω_{L}^{≤}({ x ∧ y | y ∈ Ω_{L}^{≤}(S) }) = Ω_{L}^{≤}({ x ∧ y | y ∈ S }).

Proof

1. ⪯ is easily seen to be preorder relation. For antisymmetry, consider a pair S_{1}, S_{2} ∈ ℘_{n}(L, ≤) such that S_{1} ⪯ S_{2} and S_{2} ⪯ S_{1}. If x_{1} ∈ S_{1} then there exists x_{2} ∈ S_{2} with x_{1} ≤ x_{2}. But then again there exists x′_{1} ∈ S_{1} such that x_{1} ≤ x_{2} ≤ x′_{1}. By non-redundancy we must have x_{1} = x_{2} = x′_{1}, so that x_{1} ∈ S_{2} and S_{1} ⊆ S_{2}. A symmetric argument proves that, indeed, S_{1} = S_{2}.

2. Observe that Ω_{L}^{≤}(S) ⊆ S and that, if S ∈ ℘_{c}(L) and x ∈ S, then either x ∈ Ω_{L}^{≤}(S) or there exists y ∈ Ω_{L}^{≤}(S) such that x < y.

3. Straightforward.

4. We have that

Ω_{L}^{≤}({ x ∧ y | y ∈ Ω_{L}^{≤}(S) })

= Ω_{L}^{≤}({ x ∧ y | y ∈ Ω_{L}^{≤}(S) }

∪ { x ∧ y | y ∈ S \ Ω_{L}^{≤}(S), ∃y′ ∈ Ω_{L}^{≤}(S) . x ∧ y ≤ x ∧ y′ }) = Ω_{L}^{≤}({ x ∧ y | y ∈ Ω_{L}^{≤}(S) } ∪ { x ∧ y | y ∈ S \ Ω_{L}^{≤}(S) }) = Ω_{L}^{≤}({ x ∧ y | y ∈ S }),

since, for each x ∈ L, S ∈ ℘_{c}(L) and y ∈ S \ Ω_{L}^{≤}(S), there is always a y′ ∈ Ω_{L}^{≤}(S) such that y < y′. The condition ∃y′ ∈ Ω_{L}^{≤}(S) . x ∧ y ≤ x ∧ y′ above is thus always satisfied by ∧ monotonicity. □

The powerset construction upgrades a domain by considering sets of elements of the base-level domain that are non-redundant with respect to the approximation ordering.

Definition 30 (Powerset constraint systems.) The powerset constraint system over a Noetherian constraint system

D̄ = ⟨D, ⊗, ⊕, 0, 1, {¯∃ ; ¯∃X̄}, {d_{X̄Ȳ} }⟩

is given by

⟨℘_{n}(D, ⊢), ⊗_{P}, ⊕_{P}, 0_{P}, 1_{P}, {¯∃ ; ¯∃X̄^{P}}, {dX̄Ȳ^{P} }⟩,

where

S_{1} ⊗_{P} S_{2} =^{def} Ω_{D}^{⊢}({ C_{1} ⊗ C_{2} | C_{1} ∈ S_{1}, C_{2} ∈ S_{2} }),
S_{1} ⊕_{P} S_{2} =^{def} Ω_{D}^{⊢}(S_{1} ∪ S_{2}),
0_{P} =^{def} ∅,
1_{P} =^{def} {1},
¯∃ ; ¯∃X̄^{P} S =^{def} Ω_{D}^{⊢}({ ¯∃ ; ¯∃X̄ C | C ∈ S }),
dX̄Ȳ^{P} =^{def} {d_{X̄Ȳ} }.

If D̄ is any constraint system, the finite powerset constraint system over D̄ is

⟨℘_{n}(D, ⊢) ∩ ℘_{f}(D), ⊗_{P}, ⊕_{P}, 0_{P}, 1_{P}, {¯∃ ; ¯∃X̄^{P}}, {dX̄Ȳ^{P} }⟩,

where all the operators are as above.

This double definition reflects the two possible uses of powerset constraint systems. One is to define concrete domains in those cases where the base-level constraint system is Noetherian. The other is when designing abstract domains, where clearly only the finite elements are of interest. In both cases, point (2) of Proposition 29 tells us that, when we deal with monotonic properties, we lose nothing if we restrict ourselves to non-redundant sets in order to capture the non-determinism of CLP languages. Of course, when the base-level c.s. is not Noetherian, one has to consider all the subsets in the design of a concrete domain.

Observe that the powerset construction completely disregards the merge operator of the base-level constraint system. Thus it can be applied to structures weaker than constraint systems, where the merge operator is missing. Instead of devoting a definition to them we rely on the fact that it is always possible to augment such structures with the trivial merge operator defined in (3.9) so to obtain a constraint system in the sense of Definition 7. This way, for example, any simple constraint system can indirectly constitute the basis for a powerset construction.

Theorem 31 Any powerset constraint system built over a Noetherian c.s. D̄,

⟨℘_{n}(D, ⊢), ⊗_{P}, ⊕_{P}, 0_{P}, 1_{P}, {¯∃ ; ¯∃X̄^{P}}, {dX̄Ȳ^{P} }⟩,

is a closed and completely distributive constraint system, where the ordering is given, for each S_{1}, S_{2} ∈ ℘_{n}(D, ⊢), by

S_{1} ⊢_{P} S_{2} ⟺ ∀C_{1} ∈ S_{1} : ∃C_{2} ∈ S_{2} . C_{1} ⊢ C_{2}.  (3.15)

For any c.s. D̄, the finite powerset c.s. built over D̄ is a distributive constraint system, where the ordering is given by (3.15), for S_{1}, S_{2} ∈ ℘_{n}(D, ⊢) ∩ ℘_{f}(D).

Proof Let D̄ be a Noetherian c.s. We start by showing that S_{1}⊗_{P}S_{2} is the glb of S_{1} and S_{2} with respect to ⊢_{P}. Clearly S_{1} ⊗_{P} S_{2} ⊢_{P} S_{1} and S_{1} ⊗_{P} S_{2} ⊢_{P} S_{2} since, for each C ∈ S_{1} ⊗_{P} S_{2} there are some C_{1} ∈ S_{1} and C_{2} ∈ S_{2} such that C_{1} ⊗ C_{2} = C. This implies C ⊢ C_{1} and C ⊢ C_{2}. Suppose now to have S ∈ ℘_{n}(D, ⊢) such that S ⊢_{P} S_{1} and S ⊢_{P} S_{2}. Then S ⊢_{P} S_{1} ⊗_{P} S_{2}, since

{∀C ∈ S : ∃C_{1} ∈ S_{1} . C ⊢ C_{1} ∀C ∈ S : ∃C_{2} ∈ S_{2} . C ⊢ C_{2} \atop ∀C ∈ S : ∃C_{1} ∈ S_{1}, C_{2} ∈ S_{2} . C ⊢ C_{1} ⊗ C_{2}}

and either C_{1}⊗C_{2} ∈ S_{1}⊗_{P}S_{2} or there exists C′ ∈ S_{1}⊗_{P}S_{2} such that C_{1} ⊗C_{2} ⊢ C′. In a similar way S_{1} ⊕_{P} S_{2} is shown to be the lub of S_{1} and S_{2} with respect to ⊢_{P}. In fact it is clear that S_{1} ⊢_{P} S_{1} ⊕_{P} S_{2} and S_{2} ⊢_{P} S_{1} ⊕_{P} S_{2}. Furthermore, if S ∈ ℘_{n}(D, ⊢) is such that S_{1} ⊢_{P} S and S_{2} ⊢_{P} S, then, since

{∀C_{1} ∈ S_{1} : ∃C′_{1} ∈ S . C_{1} ⊢ C′_{1} ∀C_{2} ∈ S_{2} : ∃C′_{2} ∈ S . C_{2} ⊢ C′_{2} \atop ∀C ∈ Ω_{D}^{⊢}(S_{1} ∪ S_{2}) : ∃C′ ∈ S . C ⊢ C′}

we have S_{1} ⊕_{P} S_{2} ⊢_{P} S. The minimum and maximum elements of the lattice are clearly ∅ and {1}, respectively. We now show, using points (3) and (4) of Proposition 29, that complete meetdistributivity holds:

S ⊗_{P} \begin{matrix}⨁ & _{P} \\ i∈I \end{matrix} S_{i} = Ω_{D}^{⊢}(\{ C ⊗ C′ | C ∈ S, C′ ∈ Ω_{D}^{⊢}(⋃_{i∈I} S_{i}) \})

= Ω_{D}^{⊢}({ C ⊗ C′ | C ∈ S, C′ ∈ ⋃_{i∈I} S_{i} }) = Ω_{D}^{⊢}(⋃_{i∈I}{ C ⊗ C′ | C ∈ S, C′ ∈ S_{i} })

= Ω_{D}^{⊢}(⋃_{i∈I} Ω_{D}^{⊢}({ C ⊗ C′ | C ∈ S, C′ ∈ S_{i} }))

= \begin{matrix}⨁ & _{P} \\ i∈I \end{matrix}(S ⊗_{P} S_{i}).

In particular we have shown that ⟨℘_{n}(D, ⊢), ⊗_{P}, ⊕_{P}, 0_{P}, 1_{P}⟩ is a distributive lattice so that G_{1}–G_{4} of Definition 7 are satisfied. G_{0}, G_{5}, and closedness are implicit in the definition. The above proof can be replayed substituting ℘_{n}(D) with ℘_{n}(D, ⊢)∩℘_{f}(D), I with {1, 2}, and, of course, omitting closedness. □

3.5.1 A Collecting Semantics for Logic Programs

When interested in monotonic properties of logic programs, a suitable collecting semantics can be defined over the domain resulting from the application of the powerset construction to the Pattern d.c.s. of Section 3.4.2, which is Noetherian.

3.5.2 Structural Analysis: More than Pattern

One possibility for obtaining more precise structural information than possible with Pattern, is to use the domain of the previous section together with a suitable widening operator ∇: ℘_{n}(|E|_{0})^{2} → ℘_{n}(|E|_{0}) to ensure termination.

A very crude widening operator for any powerset c.s. can be defined in terms of the base-level merge operator as follows:

S_{1} ∇ S_{2} =^{def} Ω_{D}^{⊢}({⨁(S_{1} ⊕_{P} S_{2})}).

Of course this destroys, as soon as it is applied, all the extraprecision gained by passing to the powerset. Things can be improved by using derived operators like

S_{1} ∇′ S_{2} =^{def} \{ \begin{matrix}S_{1} ∇ S_{2}, & if p(S_{1}, S_{2}); \\ S_{2}, & otherwise, \end{matrix}

where the predicate p(S_{1}, S_{2}) is true if, on passing from S_{1} to S_{2} (usually S_{1} and S_{2} are the results of two adjacent iterates computed during the analysis) “something has grown”.

A better widening for the current example of structural analysis can be defined along the following lines. Take S_{1}, S_{2} ∈ ℘_{n}(|E|_{0}), and let S = S_{1} ⊕_{P} S_{2}. From each C ∈ S take out all the tokens involving terms of depth greater than some fixed k; call C′ the result and S′ the set of all the C′ so obtained. Now S_{1}∇S_{2} is obtained from S′ by removing all the redundant elements. This widening roughly corresponds to the use of depth-k approximations as in [ST84, MS88]. It is still not very accurate, as it “cuts” also the structures that have not grown. A more precise widening can be obtained on these lines by restricting the “depth-k cut” to those variables such that the maximum depth of the associated patterns in S_{2} is greater than the maximum depth of the associated patterns in S_{1}.

3.6 Ask-and-Tell Constraint Systems

We now consider constraint systems having additional structure. This additional structure allows to express, at the constraint system level, that the imposition of certain constraints must be delayed until some other constraints are imposed. In [Sar93] similar constructions are called ask-and-tell constraint systems. In our construction, ask-and-tell constraint systems are built from constraint systems by regarding some kernel operators as constraints. We follow [Sar93] in considering cc as the language framework for expressing and computing with kernel operators.^{11} For this reason we will present kernel operators as cc agents. For our current purposes we need only a very simple fragment of the determinate cc language: the one of finite cc agents. This fragment is described in [SRP91] by means of a declarative semantics. Here we give an operational characterization that is better suited to our needs.

Definition 32 (Finite cc agents: syntax.) A finite cc agent over a constraint system D̄ = ⟨D, ⊗, ⊕, 0, 1, {¯∃ ; ¯∃X̄}, {d_{X̄Ȳ} }⟩ is any string generated by the following grammar:

11At least for a relevant class of them. See Section 3.6.1 for more on this subject.

A ::= tell(C) | ask(C) → A | A ∥ A

where C ∈ D. We will denote by A(D̄) the language of such strings. The following explicit definition is also given:

ask(C_{1}; …; C_{n}) → A ≡( ask(C_{1}) → A) ∥ ⋯ ∥( ask(C_{n}) → A). (3.16)

When this will not cause confusion we will freely drop the syntactic sugar, writing C and C_{1} → C_{2} where tell(C) and ask(C_{1}) → tell(C_{2}) are intended.

One of the beautiful properties of kernel operators is that they can be uniquely represented by their range, i.e., the set of their fixed points [GHK^{+}80]. The denotational semantics of finite cc agents over D̄ can thus be expressed by a function [[·]]: A(D̄) → ℘(D) defined following [SRP91].

Definition 33 (Semantics of finite cc agents.) The semantics of finite cc agents over a constraint system is given by the following equations:

[[C]] =^{def} ↓ C,  (3.17)
[[C → A]] =^{def} \overline{↓ C} ∪ [[A]],  (3.18)
[[A ∥ B]] =^{def} [[A]] ∩ [[B]].  (3.19)

Observe that the actual kernel operator A^{K} corresponding to a finite agent A ∈ A(D̄) can be recovered from [[A]] as

A^{K} =^{def} λC . sup((↓ C) ∩ [[A]]).  (3.20)

The introduction of a syntactic normal form for finite cc agents allows to simplify the subsequent semantic treatment.

Definition 34 (Finite cc agents: syntactic normal form.) The transformation η over A(D̄) is defined, by structural induction, as follows, for each C^{a}, C_{1}^{a}, C_{2}^{a}, C^{t} ∈ D and A, A_{1}, A_{2} ∈ A(D̄):

η(C^{a} → C^{t}) =^{def} \{ \begin{matrix}1 → 1, & if C^{a} ⊢ C^{t}; \\ C^{a} → (C^{a} ⊗ C^{t}), & otherwise; \end{matrix}
η(C^{t}) =^{def} 1 → C^{t};
η(C_{1}^{a} → (C_{2}^{a} → A)) =^{def} η((C_{1}^{a} ⊗ C_{2}^{a}) → A);
η(C^{a} → (A_{1} ∥ A_{2})) =^{def} η((C^{a} → A_{1}) ∥ (C^{a} → A_{2}));
η(A_{1} ∥ A_{2}) =^{def} η(A_{1}) ∥ η(A_{2}).

The following fact is easily proved.

Proposition 35 The transformation η of Definition 34 is well defined. Furthermore, if A ∈ A(D̄) then [[η(A)]] = [[A]] and η(A) is of the form

(C_{1}^{a} → C_{1}^{t}) ∥ ⋯ ∥ (C_{n}^{a} → C_{n}^{t}).

where C_{i}^{t} ⊩ C_{i}^{a} for each i = 1, …, n.

Proof Observe that each agent is in one and only one of the forms appearing in the left-hand sides of the equations. Given an agent, define its complexity as the number obtained by summing up the number 3 for each connective (i.e., ‘∥’ and ‘→’) occurring in a guarded context (i.e., in C → A the connectives in A are worth 3), and the number 1 for all the other connectives. It is immediate to verify that in the recursive equations of Definition 34 the arguments of η in the right-hand sides have complexity strictly less than the ones in the left-hand sides, while the right-hand sides of non-recursive equation are of the form C^{a} → C^{t}. Thus η is indeed a function, and its range is as stated. The fact that η is semanticpreserving can be shown by total induction on the complexity of agents. The base cases are trivial. For the induction step we have, for instance,

[[C^{a} → (A_{1} ∥ A_{2})]] = \overline{↓ C^{a}} ∪ ([[A_{1}]] ∩ [[A_{2}]])
= (\overline{↓ C^{a}} ∪ [[A_{1}]]) ∩ (\overline{↓ C^{a}} ∪ [[A_{2}]])
= [(C^{a} → A_{1}) ∥ (C^{a} → A_{2})]
= [η((C^{a} → A_{1}) ∥ (C^{a} → A_{2}))]
= [η(C^{a} → (A_{1} ∥ A_{2}))]. □

Thus, by considering only agents of the form ∥_{i=1}^{n} C_{i}^{a} → C_{i}^{t} we do not lose any generality. We will call elementary agents of the kind C^{a} → C^{t} ask-tell pairs.

Now we express the operational semantics of finite cc agents by means of rewrite rules. An agent in syntactic normal form is rewritten by applying the logical rules of the calculus modulo a structural congruence. This congruence states, intuitively, that we can regard an agent as a set of (concurrent) ask-tell pairs. The semantics of parallel composition stated in (3.19) clearly allows that.

Definition 36 (A calculus of finite cc agents.) Consider the agent 1_{A} =^{def} 1 → 1. The structural congruence of the calculus is the smallest congruence relation ≡_{s} such that ⟨A(D̄), ∥, 1_{A}⟩_{/≡_{s}} is a commutative and idempotent monoid. The reduction rules of the calculus are given in Figure 3.2. The relation ρ_{A} ⊆ A(D̄) × A(D̄) is defined, for each A, A′ ∈ A(D̄), as follows: A ρ_{A} A′ if and only if

(A = A_{1}) ∧ (A_{n} = A′) ∧ A_{1} ↦ A_{2} ↦ ⋯ ↦ A_{n} ↛

In the following we will systematically abuse the notation denoting the quotient of A(D̄) with respect to ≡_{s} simply by A(D̄).

Structure

{A_{1} ≡_{s} A′_{1} A′_{1} ⟼ A′_{2} A′_{2} ≡_{s} A_{2} \atop A_{1} ⟼ A_{2}}  {A_{1} ⟼ A′_{1} \atop A_{1} ∥ A_{2} ⟼ A′_{1} ∥ A_{2}}

Reduction r(1, 2)

{C_{1}^{a} ⊢ C_{2}^{a} C_{1}^{a} ⊗ C_{2}^{t} ⊢ C_{1}^{t} \atop (C_{1}^{a} → C_{1}^{t}) ∥ (C_{2}^{a} → C_{2}^{t}) ⟼ (C_{2}^{a} → C_{2}^{t})}

Deduction d(1, 2)

C_{1}^{t} ⊢ C_{2}^{a} C_{1}^{t} ⊬ C_{2}^{t} ; (C_{1}^{a} → C_{1}^{t}) ∥ (C_{2}^{a} → C_{2}^{t}) ⟼ (C_{1}^{a} → (C_{1}^{t} ⊗ C_{2}^{t})) ∥ (C_{2}^{a} → C_{2}^{t})

Absorption a(1, 2)

C_{1}^{a} ⊩ C_{2}^{a}  C_{1}^{a} ⊬ C_{2}^{t}  C_{1}^{t} ⊩ C_{1}^{a} ⊗ C_{2}^{t} ; (C_{1}^{a} → C_{1}^{t}) ∥ (C_{2}^{a} → C_{2}^{t}) ⟼ ((C_{1}^{a} ⊗ C_{2}^{t}) → C_{1}^{t}) ∥ (C_{2}^{a} → C_{2}^{t})

Figure 3.2: Reduction rules for finite cc agents.

Consequently, every assertion concerning A(D̄) is to be understood modulo structural congruence. In particular, an agent will be regarded as the set of its ask-tell pairs.

Lemma 37 The term-rewriting system depicted in Figure 3.2 is terminating. Thus, for each A ∈ A(D̄) there is always an A′ ∈ A(D̄) such that A ρ_{A} A′.

Proof Consider an agent in syntactic normal form A = ∥_{i=1}^{n} A_{i}. Each rewriting step results either in the removal of a pair (at most n − 1 such steps can be performed using the reduction rule), or in the strict strengthening of the ask or tell constraint of a pair. An upper bound for the number of such steps can be obtained by reasoning as follows. The tell constraint of each of the n pairs can be strengthened at most n−1 times. Thus at most n(n−1) applications of the deduction rule are possible. Similarly, the absorption rule will be applied at most n(n − 1) times. Consequently, A will be maximally rewritten in O(n^{2}) steps. □

We introduce now, following [SRP91], a normal form for finite cc agents.

Definition 38 (Semantic normal form.) [SRP91] The agent A ∈ A(D̄) is in semantic normal form if and only if A = 1_{A} or A = ∥_{i=1}^{n} C_{i}^{a} → C_{i}^{t} and, for each i, j ∈ {1, …, n}:

N_{1}. C_{i}^{t} ⊩ C_{i}^{a};

N_{2}. i ≠ j ⟹ C_{i}^{a} ≠ C_{j}^{a};

N_{3}. C_{i}^{a} ⊩ C_{j}^{a} ⟹ C_{i}^{a} ⊩ C_{j}^{t};

N_{4}. C_{i}^{t} ⊢ C_{j}^{a} ⟹ C_{i}^{t} ⊢ C_{j}^{t}.

It turns out that this normal form is indeed very strong, whence its name.

Theorem 39 [SRP91] Two agents A_{1}, A_{2} ∈ A(D̄) have the same semantic normal form if and only if [[A_{1}]] = [[A_{2}]].

The purpose of our rewriting system is to put finite cc agents into semantic normal form, preserving their original semantics.

Lemma 40 For each agent A ∈ A(D̄) in syntactic normal form, if A ρ_{A} A′ then [[A]] = [[A′]] and A′ is in semantic normal form.

Proof It is immediate to verify that the rules of the calculus preserve the syntactic normal form, thus A′ satisfies condition N_{1}.

Suppose N_{2} is not satisfied, that is, C_{i}^{a} = C_{j}^{a} and, consequently, C_{i}^{t} ≠ C_{j}^{t}, C_{i}^{t} ⊩ C_{j}^{a}, and C_{j}^{t} ⊩ C_{i}^{a}. Either C_{i}^{t} ⊬ C_{j}^{t} or C_{j}^{t} ⊬ C_{i}^{t} must hold, so we can apply the deduction rule d(i, j) or the symmetric one d(j, i), respectively.

Suppose that N_{3} is not satisfied, namely C_{i}^{a} ⊩ C_{j}^{a} and C_{i}^{a} ⊮ C_{j}^{t} (implying C_{i}^{t} ⊩ C_{j}^{a}). There are several cases. If C_{i}^{a} = C_{j}^{t} then C_{i}^{t} ⊩ C_{i}^{a} = C_{j}^{t}, thus C_{j}^{t} ⊢ C_{i}^{a} and C_{j}^{t} ⊬ C_{i}^{t}, which means d(j, i) is applicable. The other possibility is when C_{i}^{a} ⊬ C_{j}^{t} and either C_{j}^{t} ⊢ C_{i}^{t} or C_{j}^{t} ⊬ C_{i}^{t}. In the former case we have C_{j}^{t} ⊢ C_{i}^{t} ⊩ C_{i}^{a}, whence C_{i}^{a} ⊗ C_{j}^{t} = C_{j}^{t} ⊢ C_{i}^{t} and r(i, j) can fire. The latter case is further split as follows: if C_{i}^{t} ⊬ C_{j}^{t} then rule d(i, j) is applicable, otherwise we have C_{i}^{t} ⊩ C_{j}^{t}, which implies C_{i}^{t} ⊢ C_{i}^{a} ⊗ C_{j}^{t}. If this last entailment is strict the absorption rule a(i, j) can fire, if not (namely C_{i}^{t} = C_{i}^{a} ⊗ C_{j}^{t}), then we can apply r(i, j).

Finally, negating N_{4} we obtain C_{i}^{t} ⊢ C_{j}^{a} and C_{i}^{t} ⊬ C_{j}^{t}, which is exactly the applicability condition for d(i, j).

Preservation of semantics is readily verified. For reduction, we show that if C_{1}^{a} ⊢ C_{2}^{a} and C_{1}^{a} ⊗ C_{2}^{t} ⊢ C_{1}^{t} then

[[(C_{1}^{a} → C_{1}^{t}) ∥ (C_{2}^{a} → C_{2}^{t})]] = [[C_{2}^{a} → C_{2}^{t}]],

or, equivalently,

∀C ∈ D : [[C_{2}^{a} → C_{2}^{t}]]^{K}(C) ⊢ [[C_{1}^{a} → C_{1}^{t}]]^{K}(C).

In fact, if C ⊬ C_{1}^{a} then [[C_{1}^{a} → C_{1}^{t}]]^{K}(C) = C and the thesis hold by extensivity of [[C_{2}^{a} → C_{2}^{t}]]^{K}. If C ⊢ C_{1}^{a} then C ⊢ C_{2}^{a} and

[[C_{2}^{a} → C_{2}^{t}]]^{K}(C) = C ⊗ C_{2}^{t}
= C ⊗ C_{1}^{a} ⊗ C_{2}^{t}
⊢ C ⊗ C_{1}^{t}
= [[C_{1}^{a} → C_{1}^{t}]]^{K}(C).

The reader is also referred to [SRP91] where less constrained versions of deduction and absorption are reported as laws L12 and L11, respectively. □

Proposition 41 The term-rewriting system depicted in Figure 3.2 is strongly normalizing. Thus the relation ρ_{A} is indeed a function ρ_{A} : A(D̄) → A(D̄).

Proof Let A ∈ A(D̄) in syntactic normal form, and suppose A ρ_{A} A′ and A ρ_{A} A′′. By Lemma 40 both A′ and A′′ are in semantic normal form, and [[A′]] = [[A]] = [[A′′]]. By Theorem 39 we can conclude that A′ = A′′.

The final result is now easily obtained.

Corollary 42 For A_{1}, A_{2} ∈ A(D̄) we have ρ_{A}(A_{1}) = ρ_{A}(A_{2}) if and only if [[A_{1}]] = [[A_{2}]].

Proof By Proposition 41 ρ_{A}(A_{1}) and ρ_{A}(A_{2}) are well defined while, by Lemma 40, we have [[A_{1}]] = [[ρ_{A}(A_{1})]] and [[A_{2}]] = [[ρ_{A}(A_{2})]] and both ρ_{A}(A_{1}) and ρ_{A}(A_{2}) are in semantic normal form. Thus ρ_{A}(A_{1}) = ρ_{A}(A_{2}) if and only if A_{1} and A_{2} have the same normal form. By Theorem 39 this is equivalent to [[A_{1}]] = [[A_{2}]]. □

The situation here is almost identical to the one of Definition 25, in that we have a domain-independent strong normal form also for the present class of constraints (i.e., agents) incorporating the notion of dependency.

Definition 43 (Elements.) The elements of A(D̄) are the fixed points of the inference map ρ_{A}. The set of elements of A(D̄) will be denoted by |A(D̄)|. Thus

|A(D̄)| =^{def} { A ∈ A(D̄) | ρ_{A}(A) = A }.

We are now in position to introduce a new class in our hierarchy of constraint systems. Again we present only the finite fragment of the constraint system. Here, the finite elements are precisely the finite cc agents in |A(D̄)|.

Definition 44 (Ask-and-tell constraint system.) Given a con-

straint system D̄ = ⟨D, ⊗, ⊕, 0, 1, {¯∃ ; ¯∃X̄}, {d_{X̄Ȳ} }⟩, let A = |A(D̄)|.

Then let 0_{A}, 1_{A} ∈ A, and ⊗_{A} : A × A → A be given, for each A_{1}, A_{2} ∈ A, by

0_{A} =^{def} 1 → 0,
1_{A} =^{def} 1 → 1,
A_{1} ⊗_{A} A_{2} =^{def} ρ_{A}(A_{1} ∥ A_{2}).

The projection operators ¯∃ ; ¯∃X̄^{A} : A → A are given, for each X̄ ⊆_{f} Vars and A ∈ A, by

¯∃ ; ¯∃X̄^{A} A =^{def} ρ_{A}(A | X̄),

where

A | X̄ =^{def} \{ (¯∃ ; ¯∃X̄ C^{a} → ¯∃ ; ¯∃X̄ C^{t}) | \begin{matrix}(C^{a} → C^{t}) ∈ A \\ ((1 → ¯∃ ; ¯∃X̄ C^{a}) ⊗_{A} A) \\ ⊢_{A} (1 → C^{a}) \end{matrix} \}.

Finally, let ⊕_{A} : A×A → A be an operator satisfying the conditions G_{2} and G_{4} of Definition 7. For any indexed family {dX̄Ȳ^{A} }_{X̄,Ȳ ∈Vars^{⋆}} of elements of A, we will call

⟨A(D̄), ⊗_{A}, ⊕_{A}, 0_{A}, 1_{A}, {¯∃ ; ¯∃X̄^{A}}, {dX̄Ȳ^{A} }⟩

an ask-and-tell constraint system over D̄ and ⊕_{A}.

Notice that, as far as the diagonal elements are concerned, we have left complete freedom. This is because, in an ask-and-tell construction, the induced diagonals dX̄Ȳ^{A} = 1 → d_{X̄Ȳ} are not necessarily a good choice (see Section 3.6.3 for a simple example).

The projection operators are indeed quite complicated. The problem originates from the requirement N_{3} of the normal form of Definition 38. This requirement enforces the need of the absorption rule in the calculus. The rule, by strengthening the ask-constraint of pairs, introduces “false dependencies”. Consider, for instance, a constraint system where elements include the finite subsets of { p(X), q(X), r(X) | X ∈ Vars } and the operators ⊗ and ⊕ are set union and intersection, respectively. The non-normalized cc agent over this constraint system

A = 1 → {q(Y)} ∥ {p(X)} → {r(X), q(Y)}

is normalized, by means of the absorption rule, to

A′ = 1 → {q(Y)} ∥ {p(X), q(Y)} → {r(X), q(Y)}.

The absorption rule has thus introduced the dependency of r(X) from q(Y), which is indeed false in the context of A′ (as it was in the context of A). A definition of the projection operators not taking into account this phenomenon would cause the inaccurate result ¯∃ ; ¯∃X^{A} A′ = 1_{A}. The projection operators given in Definition 44, instead, recognize the false dependency by noting that

{p(X)} = ¯∃ ; ¯∃_{X}{p(X), q(Y)}

is, in the context of A′, equivalent to {p(X), q(Y)}, that is

1 → {p(X)} ∥ A′ ⊢_{A} 1 → {p(X), q(Y)}.

We can thus obtain the expected result ¯∃ ; ¯∃X^{A} A′ = {p(X)} → {r(X)}. We will see in a moment other problems provoked by the absorption rule and, in turn, by the normal form we employ for agents.

It is interesting to notice that the semantic normal form, while making the task of recognizing equivalent agents trivial (they are equivalent if and only if they have exactly the same pairs), is not so useful in order decide when an agent entails another one. An intuitive explanation of this fact is due to Enea Zaffanella, and it is given in Figure 3.3.

Theorem 45 If D̄ is a constraint system, so is

⟨|A(D̄)|, ⊗_{A}, ⊕_{A}, 0_{A}, 1_{A}, {¯∃ ; ¯∃X̄^{A}}, dX̄Ȳ^{A} ⟩.

Proof An ask-tell pair is a constraint by virtue of point (3) in Definition 5, viewing ‘→’ as a binary (meta-level) predicate. Thus condition G_{0} is true by point (2) of the same definition. ⟨A(D̄), ⊗_{A}, 1_{A}⟩ is immediately verified being a commutative and idempotent monoid with zero element 0_{A}, thus satisfying axioms G_{1} and G_{3}. The requirements G_{2}, G_{4}, and G_{5} are immediate consequences of the definition of ask-and-tell c.s. □

3.6.1 Merge Operators

Even though the ask-and-tell construction is parameterized with respect to a merge operator, it is possible to induce such an operator from the one of the base-level constraint system. Since this is a problematic point we proceed with care.

Suppose that the base-level constraint system D̄ is a lattice. Thus kernel operators over D̄ form again a lattice, where the lub is given, for k_{1} and k_{2} kernel operators and for each C ∈ D, by

(k_{1} ⊔ k_{2})(C) =^{def} k_{1}(C) ⊕ k_{2}(C), for C ∈ D,  (3.21)

whose fixed points are

(k_{1} ⊔ k_{2})(D) = { C_{1} ⊕ C_{2} | C_{1}, C_{2} ∈ k_{1}(D) ∪ k_{2}(D) }.

[figure: 1; a_{1}; a_{2}; t_{1}; a; t_{2}; b_{1}; b_{2}; 0]

The agent (a_{1} → t_{1} ∥ a_{2} → t_{2}) is normalized. However, even though

(a_{1} → t_{1} ∥ a_{2} → t_{2}) ⊢_{A} (a → 0),

we have

(a_{1} → t_{1}) ⊬_{A} (a → 0)

and

(a_{2} → t_{2}) ⊬_{A} (a → 0).

Figure 3.3: The semantic normal form does not help deciding the entailment.

In terms of kernel operators, as pointed out in [Sar93], this can be thought of as a kind of determinate disjunction: k_{1}⊔k_{2} gives, on any input C, the strongest common information between k_{1} and k_{2}. The computational significance of this concept has been first recognized in [VSD92a], where determinate disjunction allows for significant improvements in some constraint propagation algorithms.

The problem is that, even when k_{1} and k_{2} are represented by finite cc agents A_{1} and A_{2}, namely k_{1} = A_{1}^{K} and k_{2} = A_{2}^{K}, we have no guarantees whatsoever that k_{1} ⊔ k_{2} is representable by a finite cc agent.^{12} In other words, (syntactic) finite cc agents are not, in general, closed under the (semantic) lub operation. As a consequence, we must content ourselves with upper bounds (unless we are willing to enrich our representation language with a construct like A_{1} + A_{2} expressing determinate disjunction, and we are not). Observe that the very precise effect of (3.21) can be obtained (at a consequently high cost) by applying a powerset construction (Section 3.5) to the ask-and-tell constraint system considered. This way, when merging two (non-redundant) agents we will keep both of them, thus realizing, in practice, the ‘+’ construct mentioned above. If we do that, obviously, there is no need at all to define a merge operator at the ask-and-tell level.

In our general situation, the base-level constraint system D̄ might not be a lattice, and (3.21) might not define a kernel operator. In these cases, an upper bound on the poset of kernel operators over D̄ can be given as

(k_{1} ˜⊔ k_{2})(C) =^{def} C ⊗ (k_{1}(C) ⊕ k_{2}(C)), for C ∈ D,  (3.22)

which, still, is not guaranteed to correspond to any finite cc agent over D̄.

12For the interested reader: consider a constraint system with distinct elements {0, 1} ∪ {C_{i}}_{i∈ℕ_{0}} and such that C_{i} ⊢ C_{j} if and only if 1 ≤ i ≤ j. The lub of 1 → C_{0} and 1 → C_{1} is not expressible by a finite cc agent. The same happens if the ordering is such that C_{i} ⊬ C_{j} for all i and j.

We stress again that our non-commitment to lattices in the general definition of constraint systems (Section 3.2.3) is not merely dictated by the desire of freely managing the complexity/precision tradeoff. In cases like the one at hand we have no other sensible choice due to representation problems.

Our study of computable merge operators starts with a simple operation merging two (not necessarily normalized) agents into one. This is done, roughly speaking, by taking the meet of the ask constraints, and the merge of the tell constraints.

Definition 46 (Merge operator over agents.) Consider a constraint system D̄ = ⟨D, ⊗, ⊕, 0, 1, {¯∃ ; ¯∃X̄}, {d_{X̄Ȳ} }⟩, and any two finite cc agents over D̄ in syntactic normal form:

A_{1} = ∥_{i=1}^{n} C_{i}^{a} → C_{i}^{t} and A_{2} = ∥_{j=1}^{m} D_{j}^{a} → D_{j}^{t}.

Then

A_{1} ˜⊕_{A} A_{2} =^{def} ∥_{i=1}^{n} ∥_{j=1}^{m}(C_{i}^{a} → C_{i}^{t}) ˜⊕_{A} (D_{j}^{a} → D_{j}^{t}),  (3.23)

where, if we define C_{ij}^{a} =^{def} C_{i}^{a} ⊗ D_{j}^{a} and C_{ij}^{t} =^{def} C_{i}^{t} ⊕ D_{j}^{t}, we have

(C_{i}^{a} → C_{i}^{t}) ˜⊕_{A} (D_{j}^{a} → D_{j}^{t}) =^{def}

\{ \begin{matrix}1_{A}, & if C_{ij}^{a} ⊢ C_{ij}^{t}; \\ C_{ij}^{a} → (C_{ij}^{a} ⊗ C_{ij}^{t}), & otherwise. \end{matrix} (3.24)

It is easy to see that this syntactic operation corresponds, at the semantic level, to an upper bound.

Proposition 47 If A_{1} and A_{2} are as stated in Definition 46, then A_{1} ˜⊕_{A} A_{2} is in syntactic normal form. Furthermore, we have both [[A_{1}]] ⊆ [[A_{1} ˜⊕_{A} A_{2}]] and [[A_{2}]] ⊆ [[A_{1} ˜⊕_{A} A_{2}]], that is, A_{1} ⊢_{A} A_{1} ˜⊕_{A}A_{2} and A_{2} ⊢_{A} A_{1} ˜⊕_{A} A_{2}.

Proof Preservation of syntactic normal form is clearly guaranteed in (3.24). Then, for each i ∈ {1, …, n} and j ∈ {1, …, m}, we have C_{i}^{a} ⊗ D_{j}^{a} ⊢ C_{i}^{a}, which implies ↓(C_{i}^{a} ⊗ D_{j}^{a}) ⊆ ↓ C_{i}^{a}, that is \overline{↓ C_{i}^{a}} ⊆ \overline{↓(C_{i}^{a} ⊗ D_{j}^{a})}. Similarly, from the correctness of the base-level merge operator, namely C_{i}^{t} ⊢ C_{i}^{t} ⊕ D_{j}^{t}, we get ↓ C_{i}^{t} ⊆ ↓(C_{i}^{t} ⊕ D_{j}^{t}). Combining these two relations we obtain, for each i and j,

\overline{↓ C_{i}^{a}} ∪ ↓ C_{i}^{t} ⊆ \overline{↓(C_{i}^{a} ⊗ D_{j}^{a})} ∪ ↓(C_{i}^{t} ⊕ D_{j}^{t}),

and thus, for each i ∈ {1, …, n},

\overline{↓ C_{i}^{a}} ∪ ↓ C_{i}^{t} ⊆ ⋂_{j=1}^{m}(\overline{↓(C_{i}^{a} ⊗ D_{j}^{a})} ∪ ↓(C_{i}^{t} ⊕ D_{j}^{t})),

whence

[[A_{1}]] = ⋂_{i=1}^{n}[[C_{i}^{a} → C_{i}^{t}]]

⊆ ⋂_{i=1}^{n}[∥_{j=1}^{m} C_{i}^{a} ⊗ D_{j}^{a} → C_{i}^{t} ⊕ D_{j}^{t}]

= [[A_{1} ˜⊕_{A} A_{2}]]

follows. A symmetric argument proves that [[A_{2}]] ⊆ [[A_{1} ˜⊕_{A} A_{2}]]. □

We now have an obvious merge operator that is completely determined by the underlying, base-level constraint system.

Definition 48 (Canonical ask-and-tell merge operator.) Let us define A =^{def} |A(D̄)|. The operator ˆ⊕_{A} : A × A → A given, for each A_{1}, A_{2} ∈ A, by

A_{1} ˆ⊕_{A} A_{2} =^{def} ρ_{A}(A_{1} ˜⊕_{A} A_{2})

is called the canonical merge operator over A induced by D̄.

Notice that canonical ask-and-tell constraint systems subsume their base-level c.s., where only “tells” are considered. In fact we have η(C_{1})⊗_{A}η(C_{2}) = η(C_{1}⊗C_{2}) and η(C_{1}) ˆ⊕_{A}η(C_{2}) = η(C_{1}⊕C_{2}).

Unfortunately, the canonical merge operator turns out to be inaccurate, due to the normal form employed for agents. Consider the ask-and-tell construction applied to the Con domain of Section 3.4.1, and the agents in normal form^{13}

A_{1} =^{def} 1 → Z ∥ XZ → XY Z and A_{2} =^{def} X → XY.

It easy to see that the canonical merge operator gives

A_{1} ˆ⊕_{A} A_{2} =^{def} ρ_{A}(A_{1} ˜⊕_{A} A_{2}) = XZ → XY Z.

If we consider the non-normalized agent A′_{1} = 1 → Z ∥ X → XY Z, we have [[A′_{1}]] = [[A_{1}]] but ρ_{A}(A′_{1} ˜⊕_{A} A_{2}) = X → XY, which is strictly stronger than A_{1} ˆ⊕_{A} A_{2}.

The problem can be tracked down, as in the case of the projection operators, to the introduction, by means of the absorption rule, of “unnecessary dependencies” needed to satisfy condition N_{3} of the semantic normal form. However, while for projection operators we had a standard solution, here the situation is more difficult. Moreover, looking at the example above, one is caught by the doubt that, perhaps, the choice of the normal form of Definition 38 was a poor one, since it is based on maximal strengthening of both tell and ask constraints. We are thus lead to the following question:

Does there exist an alternative normal form for finite cc agents that is based on maximal strengthening of tell constraints and maximal weakening of ask constraints?

13For simplicity we use juxtaposition instead of the usual set notation.

We might tentatively go as follows. Consider a Noetherian constraint system D̄ and an agent A = ∥_{i=1}^{n} C_{i}^{a} → C_{i}^{t} over D̄ in semantic normal form (Definition 38). For each j ∈ {1, …, n}, let the agent A_{−j} be given by

A_{−j} =^{def} ∥_{\begin{matrix}i=1 \\ i≠j \end{matrix}}^{n} C_{i}^{a} → C_{i}^{t},

and define the set of constraints

D_{j} =^{def} Ω_{D}^{⊢}({ C ∈ D | [C → C_{j}^{t} ∥ A_{−j}] = [[A]] }).

In words, for each ask-tell pair in the original agent A, we consider the set of minimal constraints that can be substituted in the ask part without changing the semantics of A. Observe that, for each j = 1, …, n, either D_{j} = {C_{j}^{a}} or D_{j} contains only incomparable constraints strictly weaker than C_{j}^{a}. Whenever the base-level constraint system D̄ is such to ensure that all the D_{j}’s so obtained will always be finite, then the answer to the above question is positive, and the normal form of A (in this new sense) is given by

∥_{j=1}^{n} ∥_{C∈D_{j}} C → C_{j}^{t}.

Such a normal form would give us, in the same spirit of Definition 48, a very precise merge operator while allowing for a simplification of the projection operators. However, even in the cases where this normal form exists, we remain with the problem of computing it. This requires something very different from the rewriting system of Figure 3.2, as we will need mechanisms for

1. weakening constraints (now we only strengthen them), and

2. splitting ask-tell pairs (now we only combine them).

Moreover, while our current rewriting system is local, in that each rewriting depends on only two pairs, computing the new normal form requires looking at all the agents being normalized. This, together with the fact that splitting can give rise to an exponential number of new pairs, could push us beyond polynomial complexity.

While this is certainly a subject for further study, we now give a general way of defining merge and widening operators for the askand-tell construction that are more precise than the canonical merge operator. First of all, let us deal with the problem of constraints’ weakening.

Definition 49 (Weakening.) An operation ⊚: D → D on a constraint system D̄ = ⟨D, ⊗, ⊕, 0, 1, {¯∃ ; ¯∃X̄}, {d_{X̄Ȳ} }⟩ that satisfies for each C_{1}, C_{2} ∈ D:

Q_{1}. (C_{1} ⊚ C_{2}) ⊗ C_{1} = C_{1},

Q_{2}. (C_{1} ⊚ C_{2}) ⊗ C_{2} = C_{1} ⊗ C_{2},

Q_{3}. (C_{1} ⊚ C_{2}) ⊚ C_{2} = C_{1} ⊚ C_{2},

is said a weakening operator for D̄.

The intuitive explanation of this axiomatization is as follows. Condition Q_{1}, which can be restated as C_{1} ⊢ C_{1} ⊚ C_{2}, means that the weakening operation is correct (it does not add anything). Condition Q_{2} states that weakenings are not too aggressive: a weakened constraint can be restored by adding what was taken out. Q_{3} says that taking out twice the same thing is pointless. Observe that these conditions are very weak, while being sufficient for what follows.

A class of powerful weakening operators is defined next.

Definition 50 (Best weakening operator.) A weakening operator over a constraint system D̄ is a best weakening if and only if, for each C_{1}, C_{2} ∈ D, Q_{4}. ∀C ∈ D : C ⊗ C_{2} = C_{1} ⊗ C_{2} ⟹ C ⊢ (C_{1} ⊚ C_{2}).

Notice that conditions Q_{2} and Q_{4} are sufficient to define best weakenings, as Q_{1} and Q_{3} necessarily follow. Best weakenings are also monotonic on the first argument and anti-monotonic on the second one.^{14}

Example 51 (Weakening over intervals.) Consider a domain for numerical bounds analysis based on intervals. For instance, take the simple constraint system of Section 3.3.4, restricted to the intervals component, and apply to it the determinate c.s. construction. A weakening operator can be defined along the following lines, considering, for simplicity, only closed intervals:

[ l_{1}, u_{1} ] ⊚ [ l_{2}, u_{2} ] =^{def} [ l, u],

where

l =^{def} \{ \begin{matrix}−∞, & if l_{1} ≤ l_{2}; \\ l_{1}, & otherwise; \end{matrix} and u =^{def} \{ \begin{matrix}+∞, & if u_{1} ≥ u_{2}; \\ u_{1}, & otherwise. \end{matrix}

Such an operator is easily verified being a weakening. It is also monotonic on the first argument and anti-monotonic on the second one. All these extra-properties, however, are not necessary for what follows.

The following lemma is useful for what follows.

Lemma 52 Let ⊚ be a weakening over a c.s. D̄. For each C_{1}, C_{2} ∈ D, if C ∈ D is such that C ⊢ C_{2} and C ⊬ C_{1}, then C ⊬ (C_{1} ⊚ C_{2}).

14The connection between our weakenings and the class of weak relative pseudo-complements over meet-semilattices, recently introduced in [GPR95], needs to be studied.

Proof Suppose that C ⊢ C_{2}, C ⊬ C_{1}, and C ⊢ (C_{1} ⊚ C_{2}). In other words, we have C ⊗ C_{2} = C, C ⊗ C_{1} ≠ C, C ⊗ (C_{1} ⊚ C_{2}) = C, and, by axiom Q_{2}, (C_{1} ⊚ C_{2}) ⊗ C_{2} = C_{1} ⊗ C_{2}. So,

C ⊗ C_{2} = (C ⊗ (C_{1} ⊚ C_{2})) ⊗ C_{2}
= C ⊗ ((C_{1} ⊚ C_{2}) ⊗ C_{2})
= C ⊗ (C_{1} ⊗ C_{2})
= (C ⊗ C_{2}) ⊗ C_{1}
= C ⊗ C_{1}
≠ C,

thus reaching the absurd conclusion C ⊬ C_{2}. □

We are now in position to define a class of procedures for weakening the ask constraints of finite cc agents while preserving the semantics. As this operation is somewhat opposite to the absorption rewrite rule of our rewriting system, we call it de-absorption. This involves the possibility of splitting ask-tell pairs.

Definition 53 (De-absorption step.) Let A = ∥_{i=1}^{n} C_{i}^{a} → C_{i}^{t} be an agent in syntactic normal form over a c.s. D̄, and let ⊚ be a weakening over D̄. Then A′ is obtained from A by means of a de-absorption step based on ⊚ if and only if h, k ∈ {1, …, n} are such that C_{h}^{a} ⊢ C_{k}^{a}, the condition C_{h}^{a} ≠ (C_{h}^{a} ⊚ C_{k}^{t}) ⊗ C_{k}^{a} holds, and

A′ = ((C_{h}^{a} ⊚ C_{k}^{t}) ⊗ C_{k}^{a} → C_{h}^{t}) ∥ A.

Observe that any de-absorption step results in the strict weakening of an ask constraint. In fact, we have C_{h}^{a} ⊢ (C_{h}^{a} ⊚ C_{k}^{t}) by Q_{1}, and C_{h}^{a} ⊢ C_{k}^{a} by hypothesis, thus C_{h}^{a} ⊩ (C_{h}^{a} ⊚ C_{k}^{t}) ⊗ C_{k}^{a}.

Lemma 54 If an agent A′ is obtained by a de-absorption step from A, then [[A′]] = [[A]].

Proof In the hypotheses of Definition 53, let D_{h}^{a} =^{def} (C_{h}^{a}⊚C_{k}^{t})⊗C_{k}^{a}. We will prove that semantics is locally preserved, namely

[[C_{h}^{a} → C_{h}^{t} ∥ C_{k}^{a} → C_{k}^{t}]] = [[D_{h}^{a} → C_{h}^{t} ∥ C_{k}^{a} → C_{k}^{t}]].

From the hypotheses we have C_{h}^{t} ⊩ C_{h}^{a} ⊢ C_{k}^{a} and C_{h}^{t} ⊩ D_{h}^{a} ⊢ C_{k}^{a}. We can thus derive the following facts:

• since C_{h}^{t} ⊩ C_{k}^{a} we have ↓ C_{h}^{t} ⊂ ↓ C_{k}^{a} and ↓ C_{h}^{t} ∩ \overline{↓ C_{k}^{a}} = ∅;

• since C_{h}^{a} ⊢ C_{k}^{a} we have \overline{↓ C_{k}^{a}} ⊆ \overline{↓ C_{h}^{a}} and \overline{↓ C_{h}^{a}} ∩ \overline{↓ C_{k}^{a}} = \overline{↓ C_{k}^{a}};

• similarly, from D_{h}^{a} ⊢ C_{k}^{a} we have \overline{↓ D_{h}^{a}} ∩ \overline{↓ C_{k}^{a}} = \overline{↓ C_{k}^{a}};

• since C_{h}^{t} ⊩ C_{h}^{a} we have ↓ C_{h}^{t} ⊂ ↓ C_{h}^{a} and ↓ C_{h}^{t} ∩ \overline{↓ C_{h}^{a}} = ∅;

• similarly, from C_{h}^{t} ⊩ D_{h}^{a} we have ↓ C_{h}^{t} ∩ \overline{↓ D_{h}^{a}} = ∅;

• since C_{k}^{t} ⊩ C_{k}^{a} we have ↓ C_{k}^{t} ⊂ ↓ C_{k}^{a} and ↓ C_{k}^{t} ∩ \overline{↓ C_{k}^{a}} = ∅. These allow us to state that

[[C_{h}^{a} → C_{h}^{t} ∥ C_{k}^{a} → C_{k}^{t}]] = (\overline{↓ C_{h}^{a}} ∪ ↓ C_{h}^{t}) ∩ (\overline{↓ C_{k}^{a}} ∪ ↓ C_{k}^{t})
= (\overline{↓ C_{h}^{a}} ∩ \overline{↓ C_{k}^{a}}) ∪ (\overline{↓ C_{h}^{a}} ∩ ↓ C_{k}^{t}) ∪
(↓ C_{h}^{t} ∩ \overline{↓ C_{k}^{a}}) ∪ (↓ C_{h}^{t} ∩ ↓ C_{k}^{t})
= \overline{↓ C_{k}^{a}} ∪ (\overline{↓ C_{h}^{a}} ∩ ↓ C_{k}^{t}) ∪ (↓ C_{h}^{t} ∩ ↓ C_{k}^{t})
= \overline{↓ C_{k}^{a}} ⊎ (\overline{↓ C_{h}^{a}} ∩ ↓ C_{k}^{t}) ⊎ (↓ C_{h}^{t} ∩ ↓ C_{k}^{t}),

and, by the same reasoning,

[[D_{h}^{a} → C_{h}^{t} ∥ C_{k}^{a} → C_{k}^{t}]] = \overline{↓ C_{k}^{a}} ⊎ (\overline{↓ D_{h}^{a}} ∩ ↓ C_{k}^{t}) ⊎ (↓ C_{h}^{t} ∩ ↓ C_{k}^{t}), where ⊎ denotes disjoint union. Since C_{h}^{a} ⊩ D_{h}^{a} implies

(\overline{↓ C_{h}^{a}} ∩ ↓ C_{k}^{t}) ⊇ (\overline{↓ D_{h}^{a}} ∩ ↓ C_{k}^{t}), what we are left to show is that

(\overline{↓ C_{h}^{a}} ∩ ↓ C_{k}^{t}) ⊆ (\overline{↓ D_{h}^{a}} ∩ ↓ C_{k}^{t}),

that is to say ∀C ∈ D : (C ⊢ C_{k}^{t} ∧ C ⊬ C_{h}^{a}) ⟹ C ⊬ D_{h}^{a}. Now Lemma 52 comes in handy, as from C ⊢ C_{k}^{t} and C ⊬ C_{h}^{a} we can derive C ⊬ (C_{h}^{a} ⊚ C_{k}^{t}) and thus C ⊬ (C_{h}^{a} ⊚ C_{k}^{t}) ⊗ C_{k}^{a} = D_{h}^{a}. □

We now define the promised class of procedures.

Definition 55 (De-absorption procedure.) A de-absorption procedure is any algorithm transforming a finite cc agent in syntactic normal form, such that it can be characterized as follows:

Phase 1. Transform the input agent A into A′ by performing any number of de-absorption steps;

Phase 2. Transform A′ into the output agent A′′ by applying the rewriting system of Figure 3.2 restricted to the structural and reduction rules.

A de-absorption procedure will be called maximal if it applies all the possible de-absorption steps.

It is now possible to prove the following result.

Theorem 56 Any de-absorption procedure preserves the semantics of agents.

Proof Immediate from Lemma 54 and Lemma 40. □

In all those cases where we have a de-absorption procedure that is a function over |A(D̄)| we have an obvious way to define a merge operator: by applying the syntactic merge operator of Definition 48 to de-absorbed agents.

Definition 57 (Merge operator with de-absorption.) Let D̄ be a constraint system and δ_{⊚}: |A(D̄)| → A(D̄) be a de-absorption procedure. The merge operator based on δ_{⊚} is given, for each A_{1}, A_{2} ∈ |A(D̄)|, by

A_{1} ˙⊕_{A} A_{2} =^{def} ρ_{A}(δ_{⊚}(A_{1}) ˜⊕_{A} δ_{⊚}(A_{2})).

Any such operator, by virtue of Theorem 56 and Proposition 47, is clearly a merge operator in the sense of Definition 7. De-absorption procedures that are not functions are still useful for designing widening operators.

We now quickly show some examples of ask-and-tell constraint systems. For the more exciting things we have to wait until the next section, where combination of constraint domains are introduced.

3.6.2 More Bounds and Relations Analysis for Numeric Domains

Ask-and-tell constraint systems are suitable for modeling approximate inference techniques that are very useful in a practical setting. Following Section 3.3.4, there is another technique that is used for the analysis described in Chapter 5: relational arithmetic [Sim86]. This technique allows to infer constraints on the qualitative relationship of an expression to its arguments. Consider the simple constraint system of Section 3.3.4, and apply to it the determinate construction of Section 3.4. Now apply the ask-and-tell construction to the result. Relational arithmetic can be described by a number of (concurrent) agents. Here are some of them, where x and y are arithmetic expressions, and ▷◁ ranges in R =^{def} {=, ≠, ≤ <, ≥, >}:

ask(x ▷◁ 0) → tell((x + y) ▷◁ y)
ask(x > 0 ∧ y > 0 ∧ x ▷◁ 1) → tell((x ∗ y) ▷◁ y)
ask(x > 0 ∧ y < 0 ∧ x ▷◁ 1) → tell(y ▷◁ (x ∗ y))
ask(x > 0 ∧ y < 0 ∧ x ▷◁ −y) → tell(−1 ▷◁ (x/y))
ask(x ▷◁ y) → tell(e^{x} ▷◁ e^{y})

An example of inference is deducing that X + 1 ≤ Y + 2X + 1 from the hypotheses X ≥ 0 and Y ≥ 0. Notice that there is no restriction to linear constraints.

3.6.3 Definiteness Analysis: Def

The prototypical example of data-flow analysis taking advantage of dependency information is definiteness analysis. In our setting a domain for definiteness can be obtained as follows. Take the atomic s.c.s. of Section 3.3.1. Apply to it the determinate construction as outlined in Section 3.4.1. Now apply the ask-and-tell construction to the result, with the merge operator obtained along the lines of Definition 57 choosing:

1. diagonal elements like^{15}

dXY^{A} =^{def} {X} → {X, Y } ∥ {Y } → {X, Y };

2. set-theoretic difference as weakening operator;

3. the maximal de-absorption procedure (i.e., the one that applies all the possible de-absorption steps).

15Here only in the monadic case, for simplicity.

It can be shown that the domain so obtained is Def [Dar91, AMSS]. Its elements can keep track of non-trivial dependencies like the ones induced by symbolic and numeric constraints. For example, the dependencies of A = f(B, C) and A + B + C = 0 are captured, respectively, by the agents

{A} → {B, C} ∥ {B, C} → {A},

and

{A, B} → {C} ∥ {A, C} → {B} ∥ {B, C} → {A}.

This example gives us the possibility of pointing out that the entire business of weakenings and de-absorption procedures is not something we can easily avoid. When using definite sentences to represent dependencies, as in our case and in the representations for Def studied in [AMSS], obtaining a maximal weakening of the antecedents is crucial for obtaining precise merge operators, let alone for computing the join when it exists. Our present requirement of employing maximal de-absorption corresponds to the requirement, in the representations studied in [AMSS], of the sentences being in orthogonal form (which has its costs, since orthogonality must be obtained and preserved by all the domain’s operations). In [AMSS] a merge operator is also presented, for the representation RCNF_{Def}, intended to trade precision for efficiency. It does that by not insisting on orthogonality, which in our setting corresponds to the use of a partial de-absorption procedure.

3.6.4 Definiteness Analysis: More than Pos

Pos is (like Def ) a domain of boolean functions [CFW91, AMSS]. It consists precisely of those functions assuming the true value under the everything-is-true assignment. In [AMSS] it is shown that Pos is strictly more precise than Def for groundness analysis. If we apply the powerset construction of Section 3.5 to the ask-and-tell c.s. of the previous section we obtain a very precise (and complex) domain for simple dependencies. In [GR96] it is referred to as ℧(Def) (where ℧ denotes disjunctive completion) and is shown to be equivalent to ℧(Pos). On the other hand, in [FR94] it has been shown that ℧(Pos) is strictly more precise than Pos, even though this extraprecision is not needed for definiteness analysis.

3.7 Combination of Domains

It is well-known that different data-flow analyses can be combined together. In the framework of abstract interpretation this can be achieved by means of standard constructions such as reduced product and down-set completion [CC79, CC92a]. The key point is that the combined analysis can be more precise than each of the component ones for they can mutually improve each other. However, the degree of cross-fertilization is highly dependent on the degree and quality of interaction taking place among the component domains.

We now propose a general methodology for domain combination with asynchronous interaction. The interaction among domains is asynchronous in that it can occur at any time, or, in other words, it is not synchronized with the domain’s operations.

This is achieved by considering ask-and-tell constraint systems built over product constraint systems. These constraint systems allow to express communication among domains in a very simple way. They also inherit all the semantic elegance of concurrent constraint programming languages, which provide the basis for their construction. Recently, a methodology for the combination of abstract domains has been proposed in [CLV94], which is directly based on low-level actions such as tests and queries. While the approach in [CLV94] is immediately applicable to a wider range of analyses (including the ones dealing with non-monotonic properties) the approach we follow here for our restricted set of analyses has the merit of being much more elegant.

We start with a finite set of constraint systems each expressing some properties of interest, and we wish to combine them so to as:

1. perform all the analyses at the same time; and

2. have the domains cooperate to the intent of mutually improving each other.

The first goal is achieved by considering the product of the given constraint systems.

3.7.1 Product Constraint Systems

The product construction over constraint systems is absolutely standard.

Definition 58 (Direct product of constraint systems.) Given a finite family of constraint systems

D̄_{i} = ⟨D_{i}, ⊗_{i}, ⊕_{i}, 0_{i}, 1_{i}, {¯∃ ; ¯∃i ; X̄}, {d_{X̄Ȳ}^{i} }⟩, for i = 1, …, n,

the product constraint system of D̄_{1}, …, D̄_{n} is the algebraic direct product of D̄_{1}, …, D̄_{n}, and is denoted by

∏_{i=1}^{n} D̄_{i} = ⟨D_{×}, ⊗_{×}, ⊕_{×}, 0_{×}, 1_{×}, {¯∃ ; ¯∃× ; X̄}, {d_{X̄Ȳ}^{×} }⟩.

Thus all the operations of a product c.s. are defined coordinate-wise and, in particular, we have

⟨C′_{1}, …, C′_{n}⟩ ⊢_{×} ⟨C′′_{1}, …, C′′_{n}⟩ ⟺ ∀i = 1, …, n : C′_{i} ⊢ C′′_{i}.

An alternative way of obtaining a product constraint system is to start from a collection of simple constraint systems and then to apply the determinate construction.

Definition 59 (Product of simple constraint systems.) The product of a finite family of simple constraint systems,

S_{i} = ⟨C_{i}, ⊢_{i}, ⊥_{i}, ⊤_{i}⟩, for i = 1, …, n,

is the structure given by

∏_{i=1}^{n} S_{i} =^{def} ⟨C_{×}, ⊢_{×}, ⊥_{×}, ⊤_{×}⟩,

where the product tokens are

C_{×} =^{def} { (c_{1}, ⊤_{2}, …, ⊤_{n}) | c_{1} ∈ C_{1} }
∪ { (⊤_{1}, c_{2}, ⊤_{3}, …, ⊤_{n}) | c_{2} ∈ C_{2} }
⋮
∪ { (⊤_{1}, …, ⊤_{n−1}, c_{n}) | c_{n} ∈ C_{n} }
∪ {⊥_{×}}.

The product entailment is defined as the least relation satisfying conditions E_{1}–E_{5} of Definition 23 and the following ones, for each C ∈ ℘_{f}(C_{×}):

π_{1}(C) ⊢_{1} c_{1}  ⟹ C ⊢_{×} (c_{1}, ⊤_{2}, …, ⊤_{n}),
⋮  ⋮  ⋮
π_{n}(C) ⊢_{n} c_{n}  ⟹ C ⊢_{×} (⊤_{1}, …, ⊤_{n−1}, c_{n}).

Finally, ⊥_{×} =^{def} (⊥_{1}, …, ⊥_{n}) and ⊤_{×} =^{def} (⊤_{1}, …, ⊤_{n}).

The product simple constraint system so obtained is to be used, together with a suitable merge operator, as the given s.c.s. in the construction of Definition 26. This yields a product constraint system.

Taking the product of constraint systems, we have realized the simplest form of domain combination. It corresponds to the direct product construction of [CC79], allowing for different analyses to be carried out at the same time. Notice that there is no communication at all among the domains.

However, as soon as we consider the ask-and-tell constraint system built over the product, we can express asynchronous communication among the domains in complete freedom. At the very least we would like to have the smash product among the component domains. This is realized by the agent

∥_{i=1}^{n} 0_{i} → 0_{×}.  (3.25)

To say it operationally, the smash agent globalizes the (local) failure on any of the component domains. This is the only domainindependent agent we have.

Things become much more interesting when instantiated over particular constraint domains. In the CLP(R) system [JMSY92b] non-linear constraints (like X = Y ∗Z) are delayed (i.e., not treated by the constraint solver) until they become linear (e.g., until either Y or Z is constrained to take a single value). In standard semantic treatments this is modeled in the operational semantics by carrying over, besides the sequence of goals yet to be solved, a set of delayed constraints. Constraints are taken out from this set (and incorporated into the constraint store) as soon as they become linear.

We believe that this can be viewed in an alternative way that is more elegant, as it easily allows for taking into account the delay mechanism also in the bottom-up semantics, and makes sense from an implementation point of view. The basic claim is the following: CLP(R) has three computation domains: Herbrand, ℝ (well, an approximation of it), and definiteness.

In other words, it also manipulates, besides the usual ones, constraints of the kind ground^{♮}(X), which is interpreted as the variable X being definitively bound to a unique value. We can express the semantics of CLP(R) (at a certain level of abstraction) with delay of non-linear constraints by considering the ask-and-tell constraint system over the product of the above three domains. In this view, a constraint of the form X = Y ∗Z in a program actually corresponds to the agent

ask(ground^{♮}(Y); ground^{♮}(Z)) → tell(X = Y ∗ Z).

In fact, any CLP(R) user must know that X = Y ∗ Z is just a shorthand for that agent! (A similar treatment could probably be done for logic programs with delay declarations.)

Obviously, this cannot be forgotten in abstract constraint systems intended to formalize correct data-flow analyses of CLP(R). Referring back to Sections 3.3.4 and 3.6.2, when the abstract constraint system extracts information from non-linear constraints, for example with the agent

A = ask(Y > 0 ∧ Z > 0 ∧ Y ▷◁ 1) → tell((Y ∗ Z) ▷◁ Z)

of relational arithmetic, you cannot simply let X = Y ∗ Z stand by itself. By doing this you would incur the risk of overshooting the concrete constraint system (thus loosing soundness), which is unable to deduce anything from non-linear constraints. The right thing to do is to combine the numeric abstract constraint system with one for definiteness (by the product and the ask-and-tell constructions) and using, instead of A, the agent

A′ = ask(ground^{♯}(Y); ground^{♯}(Z)) → A.

Beware not to confuse ground^{♮}(X) with ground^{♯}(X). The first is the concrete one: X is definite if and only if ground^{♮}(X) is entailed in the current concrete store. In contrast, having ground^{♯}(X) entailed in the abstract constraint store at some program point, and assuming a correct definiteness analysis, means that X is certainly bound to a unique value in the concrete computation at that program point. The converse, of course, does not necessarily hold.

Let us see another example. The analysis described in [Han93] aims at the compile-time detection of those non-linear constraints that will become linear at run time. This analysis is important for remedying the limitation of CLP(R) to linear constraints by incorporating powerful (and computationally complex) methods from computer algebra as the ones employed in the RISC-CLP(Real) system [Hon93]. With the results of the above analysis this extension can be done in a smooth way: non-linear constraints that are guaranteed to become linear will be simply delayed, while only the other non-linear constraints will be treated with the special solving techniques. Thus, programs not requiring the extra power of these techniques will be hopefully recognized as such, and will not pay any penalties. The analysis of [Han93] is a kind of definiteness. One of its difficulties shows up when considering the simplest non-linear constraint: X = Y ∗ Z. Clearly X is definite if Y and Z are such. But we cannot conclude that the definiteness of Y follows from the definiteness of X and Z, as we also need the condition Z ≠ 0. Similarly, we would like to conclude that X is definite if Y or Z has a zero value. Thus we need approximations of the concrete values of variables (i.e., bounds analysis), something that is not captured by common definiteness analyses while being crucial when dealing with non-linear constraints. If we take the ask-and-tell construction over the product of a constraint system for definiteness with a numerical one, we can solve the problem. The concrete constraint X = Y ∗ Z would be abstractly compiled into the agent

ask(ground^{♯}(Y) ∧ ground^{♯}(Z)) → tell(ground^{♯}(X))
∥ ask(Y = 0; Z = 0) → tell(ground^{♯}(X))
∥ ask(ground^{♯}(X) ∧ ground^{♯}(Z) ∧ Z ≠ 0) → tell(ground^{♯}(Y))
∥ ask(ground^{♯}(X) ∧ ground^{♯}(Y) ∧ Y ≠ 0) → tell(ground^{♯}(Z)).

Of course, this is significantly more precise than the Def formula X ← Y ∧ Z.

We have thus reconciled the two running examples of this chapter (numerical bounds/relations and definiteness) by showing two examples of combination. In fact, when analyzing CLP(R) programs, there is a bidirectional flow of information: definiteness information is required for a correct handling of delayed constraints and thus for deriving more precise numerical patterns that, in turn, are used to provide more precise definiteness information. There is another obvious way in which numerical bounds and relations improve definiteness (and any other analysis, indeed): by excluding computation paths that are doomed to fail (this is modeled in a domain-independent way by the smash agent seen above). We are thus requiring a quite complicated interaction between domains. It is even more complicated if you consider that the numerical component we have sketched is the combination (in the sense of the present section) of a domain for intervals with one for arithmetic relationships (even though, for simplicity, it was not presented in that way).

3.7.2 Approximating Built-ins Behavior

The techniques we propose are suitable for approximating the behavior of several common built-ins. Consider, for instance, the functor/3 built-in. Consider a product constraint system with four components: one for simple types (for instance, the one of Section 3.3.2), one for definiteness, one incorporating numerical information (including at least signs, e.g., tokens of the kind X ≥ 0, X > 0 and X = 0), and one involving symbolic, structural information (e.g., the one of Section 3.4.2). Then, the (success) semantics of functor(T, F, N) can be approximated easily and quite precisely by means of the following finite agent over the product:

tell(symbolic(T), atom(F), ground(F))
∥ tell(integer(N), N ≥ 0, ground(N))
∥ ask(atom(T); N = 0; T = F )
→ tell(atom(T), ground(T), N = 0, T = F )
∥ ask(compound(T); N > 0; T ≠ F )
→ tell(compound(T), N > 0, T ≠ F ).

3.8 Conclusion and Future Work

We have shown a notion of constraint system that is general enough to encompass both the concrete domains of computation of actual constraint logic-based languages, and several of their abstract interpretations useful for data-flow analysis. We have also shown how these constraint systems are integrated within an appropriate framework for the definition of non-standard semantics of constraint logic-based languages. Some significant members of the introduced class of constraint systems have been presented, together with construction techniques that induce a hierarchy of domains. These domains have several nice features, both from a theoretical and an experimental viewpoint.

In particular, we have proposed a general methodology for domain combination with asynchronous interaction. In this kind of combination the communication among domains can be expressed in a very simple way. The methodology also inherits all the semantic elegance of concurrent constraint programming languages.

Ask-and-tell constraint systems have been satisfactorily implemented in the China analyzer. Of course, since we strive (but not too much) for efficient analyzers, we have not implemented them exactly as described here. We have, however, a firm theoretical basis on which to base our implementation tricks. We have also implemented domain combinations along the lines of Section 3.7. In China, following an abstract compilation approach, CLP(R) constraints are compiled into finite cc agents over a product of domains for simple types, definiteness, numerical bounds and relations, and generic patterns.

Future work includes studying in depth the problem of the semantic normal form for finite cc agents, both in general and in particular cases. The aim is to find more satisfactory solutions to the problem of merging finite cc agents. We also would like to answer the following question: are there variations of these ideas that are applicable also to analysis oriented towards non-monotonic or “non-logical” properties (like variable sharing and freeness)?

Structural Information Analysis

\underline{Contents}

4.1 Introduction . . . . . . . . . . . . . . . . . 122 4.2 Preliminaries . . . . . . . . . . . . . . . . . 127 4.3 Factoring Out Structural Information . . 129 4.4 Parametric Structural Analysis . . . . . . 133 4.5 Operations for the Analysis . . . . . . . . 135

4.5.1 Meet with Renaming Apart . . . . . . . 136 4.5.2 Unification with Occur-Check . . . . . . 137 4.5.3 Projection . . . . . . . . . . . . . . . . . 141 4.5.4 Remapping . . . . . . . . . . . . . . . . 142 4.5.5 Join and Widenings . . . . . . . . . . . 144 4.5.6 Comparing Descriptions . . . . . . . . . 146

4.6 What if the Occur-Check is Omitted? . . 148

4.6.1 Unification without Occur-Check …. 152 4.7 Conclusion . . . . . . . . . . . . . . . . . . 154

                length([], 0).
                length([H|T], N) :-
                    length(T, N-1).

Figure 4.1: List-length in CLP(H, N).

4.1 Introduction

It is important to make a clear distinction between the language CLP(H, X), where the Herbrand component H is completely separated from the domain X, and the language CLP(H_{X}), where H and X are somewhat amalgamated. Of course, CLP(H, X) languages are simpler and less expressive, though still useful. The simplicity come from the fact that interpreted terms (arithmetic expression, for instance) are not allowed to occur as leaves of Herbrand terms. A simple example program that is expressible in a language of the kind CLP(H, N), with N any numeric domain supporting linear equations, is a reversible version of the Prolog list-length program: it is given in Figure 4.1.

When interpreted terms are allowed to occur in Herbrand structures more complex programs can be built. For instance, one can express unbounded lists where interpreted terms occur. The program in Figure 4.2 makes use of this possibility. It is a variant of a program from a paper of Alain Colmerauer [Col90]. It solves a rather difficult problem: in the wording of the cited paper, the program is a “program for filling a rectangle of unknown shape by n squares of unknown but different sizes” (op. cit., p. 82).^{1}

1The version in Figure 4.2 is due to Niels Jørgensen.

4.1. Introduction  123

fill_rectangle(A,N,C) :-
A >= 1,
all_different(N,C),
fill([1,0,0,A,1],C,_,[]).
all_different(0,[]).
all_different(N + 1,[[_,_,B]|T]) :-
N >= 0,
all_different(N,T),
different(B,T).
different(_,[]).
different(B,[[_,_,C]|T]) :-
B <> C,
different(B,T).
fill([Y0,X1,Y1|L],C,[Y0,X1,Y1|L],C) :-
Y0 <= Y1.
fill([Y0,X1,Y1|L],[[X1,Y1,B]|C],L3,C3) :-
Y0 > Y1,
Y1 + B <= 1,
place_square([X1,Y1|L],[X1 + B|L1]),
fill([Y1 + B,X1 + B|L1],C,L2,C2),
fill([Y0,X1|L2],C2,L3,C3).
place_square([_,Y1,X2,Y2|L],L1) :-
Y1 = Y2,
place_square([X2,Y2|L],L1).
place_square([_,_|L],L).
place_square([X1,Y1,X2|L],[X1e,Y1,X2|L]) :-
X1 < X1e,
X1e < X2.

Figure 4.2: The fill_rectangle program.

%% Tests d'inclusion pour le solide primitif "sphere".
dedans((X,Y,Z), solide(sphere(Cx,Cy,Cz,R))) :-
    (X-Cx)*(X-Cx) + (Y-Cy)*(Y-Cy) + (Z-Cz)*(Z-Cz) <= R*R.
%% Tests d'inclusion pour le solide primitif "cylindre".
dedans((X,Y,Z), solide(cylindre((X0,Y0,Z0),(X1,Y1,Z1),R))) :-
    % vecteur directeur de l'axe de symetrie
    Vx = X1-X0, Vy = Y1-Y0, Vz = Z1-Z0,
    % le point (Xp,Yp,Zp) est sur l'axe de symetrie,...
    Xp = Vx*T + X0,
    Yp = Vy*T + Y0,
    Zp = Vz*T + Z0,
    % ... a l'interieur du cylindre...
    T >= 0, T <= 1,
    % ... et sur le plan orthogonal a l'axe contenant (X,Y,Z)
    Vx*(X-Xp) + Vy*(Y-Yp) + Vz*(Z-Zp) = 0,
    % contraindre le cylindre
    (X-Xp)*(X-Xp) + (Y-Yp)*(Y-Yp) + (Z-Zp)*(Z-Zp) <= R*R.

Figure 4.3: A fragment of csg.clpr.

Independently of the need for “unbounded containers”, it is a common CLP idiom to embed interpreted terms into Herbrand terms. As a final example we reproduce in Figure 4.3 a fragment of a program for solving intersection problems involving two solids.

The above discussion is motivated by the fact that several analysis methods presented in the literature, either assume a CLP(X) or CLP(H, X) language, or tolerate severe precision penalties when interpreted functors occur as leaves of uninterpreted functors, or are rather evasive about how the analysis technique presented is extended from CLP(X) to CLP(H_{X}).

From the experience gained with the first prototype version of China [Bag94] it was clear that, in order to attain a significant precision in the analysis of numerical constraints in CLP(H_{N}) languages, one must keep at least part of the uninterpreted terms in concrete form. Note that almost any analysis is more precise when this kind of structural information is retained to some extent: in our case the precision loss was just particularly acute.

Cortesi et al. [CLV93, CLV94] have a nice proposal in this respect. Using their terminology, they defined a generic abstract domain Pat(ℜ) that automatically upgrades a domain ℜ (which must support a certain set of elementary operations) with structural information. Their approach is limited to the analysis of logic programs, but this restriction can be easily removed. However, the presentation in [CLV93] has several drawbacks. First of all, they define a specific implementation of the generic structural domain. The implementation is forcedly cluttered with details that make the general principles difficult to understand. Moreover, they describe an implementation of the pattern component (taking care of representing the terms in concrete form) that appears to be unnecessarily complicated. Their representation of terms and subterms, while responsible for some of the intricacies in the description, does not seem to have any advantage, from the implementation point of view, with respect to more standard representations of terms (such as those employed in the Warren’s Abstract Machine and its variants [AK91]). As a consequence, standard notions from unification theory, such as instance, anti-instance, and (least) common antiinstance [LMM88], are never mentioned in [CLV93], while being implicitly present.

On the more technical side, Cortesi et al. assume explicitly that, during the analysis, no cyclic pattern (term) will be generated. This assumption, which is indeed standard, makes sense only if we pretend that the analyzed language does not omit the occur-check in the unification procedure. Unfortunately, it is well-known that many implemented CLP(H_{X}) languages (in particular, almost all Prolog systems) do omit the occur-check. The real solution to this problem would be to consider the real concrete domain implemented by the vast majority of systems: some kind of rational trees [Col84], and not finite trees. This, however, is not guaranteed to preserve the existing analysis frameworks and domains: everything ought to be recasted and justified in terms of the “new” concrete domain.

In this chapter we present, for the first time, the rational construction of a generic domain for structural analysis of CLP(H_{X}) languages: Pattern(D_{H_{X}}^{♯}), where the parameter D_{H_{X}}^{♯} is an abstract domain satisfying certain properties. The formalization of the structural domain is independent from specific implementation techniques: Pat(ℜ) (slightly extended and corrected) is a possible implementation of the domain. Reasoning at a higher level of abstraction we are able to appeal to familiar notions of unification theory. One advantage is that we can identify an important parameter (a common anti-instance function, missing in [CLV93]) that gives some control over the precision and computational cost of the resulting generic structural domain. As far as the occur-check problem is concerned, we have not been able, so far, to find a general solution that preserves the existing analysis framework. More research is required on this subject. Nonetheless, we will discuss some observations and partial solutions.

It must be stressed that the merit of Pat(ℜ) is to define a generic implementation that works on any domain ℜ providing a certain set of elementary, low-level operations. It is particularly easy to extend an existing domain in order to support the simple operations required. However, this simplicity has a high cost in terms of efficiency: the execution of many isolated small operations over the underlying domain is much more expensive than performing few macro-operations where global effects can be taken into account. The operations that the underlying domain must provide are thus more complicated in our approach. This is not a limitation, if one considers that in the actual implementation even more complex operations are used. For instance, all the abstract bindings arising from a bunch of unifications are executed in one shot, instead of one-at-a-time. See near the end of Section 6.7 for more on this subject.

4.2 Preliminaries

We assume that our pervasive set of variable symbols, Vars, contains (among others) two infinite, disjoint subsets: z and z′. Since Vars is totally ordered, z and z′ are as well:

z =^{def} (Z_{1}, Z_{2}, Z_{3}, …  (4.1)
z′ =^{def} (Z′_{1}, Z′_{2}, Z′_{3}, …  (4.2)

For any syntactic object o (a term or a tuple of terms) we will denote by vseq(o) the sequence of first occurrences of variables that are found in a depth-first, left-to-right traversal^{2} of o. For instance,

vseq((f(g(X), Y), h(X))) = (X, Y).

In order to avoid the burden of talking “modulo renaming” we will make use of two strong normal forms for tuples of terms. Specifically, the set of n-tuples in z-form is given by

T_{z}^{n} =^{def} { t̄∈ T_{Vars}^{n} | vseq(t̄) = (Z_{1}, Z_{2}, …, Z_{|vars(t̄)|}) }. (4.3)

All the tuples in z-form are contained in

T_{z}^{⋆} =^{def} ⋃_{n∈ℕ}T_{z}^{n}.  (4.4)

2Any other fixed ordering would be as good for our purposes.

By replacing z with z′ and Z_{i} with Z′_{i} in (4.3) and (4.4), we obtain similar definitions for T_{z′}^{n} and T_{z′}^{⋆}.

There is a useful device for toggling between z- and z′-forms. Let t̄∈ T_{z}^{n} ∪ T_{z′}^{n} and |vars(t̄)| = m. Then

t̄′ =^{def} \{ \begin{matrix}t̄[Z′_{1}/Z_{1}, …, Z′_{m}/Z_{m}], & if t̄∈ T_{z}^{n}; \\ t̄[Z_{1}/Z′_{1}, …, Z_{m}/Z′_{m}], & if t̄∈ T_{z′}^{n}. \end{matrix}  (4.5)

Notice that t̄′′ =^{def} (t̄′)′ = t̄.

We will make use of a normalization function η: T_{Vars}^{⋆} → T_{z}^{⋆} such that, for each t̄ ∈ T_{Vars}^{⋆}, the resulting tuple η(t̄) ∈ T_{z}^{⋆} is a variant of t̄.

Another renaming we will use is the following: for each s̄ ∈ T_{Vars}^{⋆} and each other syntactic object o such that FV (o) ⊂ z, we write ϱs̄(o) to denote

o[Z_{n+i_{1}}/Z_{i_{1}}, …, Z_{n+i_{m}}/Z_{i_{m}}],

where n = |vars(s̄)| and {Z_{i_{1}}, …, Z_{i_{m}}} = vars(o). This device will be useful for concatenating normalized term-tuples, still obtaining a normalized term-tuple. In fact, for each s̄_{1}, s̄_{2} ∈ T_{z}^{⋆} we have

s̄_{1} :: ϱs̄_{1}(s̄_{2}) ∈ T_{z}^{⋆}.  (4.6)

When V̄ ∈ Vars^{m} and t̄ ∈ T_{Vars}^{m} we use [t̄/V̄ ] as a shorthand for the substitution

[π_{1}(t̄)/π_{1}(V̄), …, π_{m}(t̄)/π_{m}(V̄)].

A couple of observations are useful for what follows. If s̄ ∈ T_{z}^{⋆} and ū ∈ T_{z}^{|vars(s̄)|} then

s̄′[ū/ vseq(s̄′)] ∈ T_{z}^{⋆}.  (4.7)

Moreover

vseq(s̄′[ū/ vseq(s̄′)]) = vseq(ū).  (4.8)

4.3 Factoring Out Structural Information

A quite general picture for the analysis of a CLP(H_{X}) language is as follows. We want to describe a (possibly infinite) set of constraint stores over a tuple of variables of interest V_{1}, …, V_{k}. These variables represent the arguments of some program predicate. Each constraint store σ can be represented (at some level of abstraction) by a formula of the kind

∃_{Δ} . ({V_{1} = t_{1}, …, V_{k} = t_{k}} ∧ C),  (4.9)

such that

{V_{1} = t_{1}, …, V_{k} = t_{k}}, with t_{1}, …, t_{k} ∈ T_{Vars},  (4.10)

is a system of Herbrand equations in solved form, C ∈ D_{X}^{♭} is a constraint on the concrete domain of X, and

Δ =^{def} vars(C) ∪ vars(t_{1}) ∪ ⋯ ∪ vars(t_{k})

is such that Δ ∩ {V_{1}, …, V_{k}} = ∅. Roughly speaking, the purpose of C is to limit the values that the (quantified) variables occurring in t_{1}, …, t_{k} can take.

It must be stressed that the concrete semantics we are outlining, while providing an adequate basis for many abstract interpretations, is “too abstract” when the properties of interest concern the internal workings of the Herbrand constraint solver. An example is structure-sharing analysis [WW96], whose aim is to determine those structure cells (elementary objects used to represent terms) that are possibly shared by more than one term representation. For example, the system

{V_{1} = f(a), V_{2} = f(a)}  (4.11)

does not say anything about structure sharing: we might have a shared f/1 cell, or two distinct ones. In the latter case we might have a shared a/0 cell, or two distinct ones. Thus, there are a total of 3 cases that cannot be distinguished by looking at (4.11), the obvious consequence being that we cannot base structure-sharing analysis on a concrete domain made up of representations like (4.9). Similar considerations apply to the choice of D_{X}^{♭}.

Let us assume that we are dealing with analyses where the internal representation of terms is irrelevant. Once variables V_{1}, …, V_{k} have been fixed, the Herbrand part of the constraint store (4.9), the system of equations (4.10), can be represented as a k-tuple of terms. Since we want to characterize any set of constraint stores, our concrete domain is

D_{H_{X}}^{♭} =^{def} ⋃_{n∈ℕ}℘(T_{z}^{n} × D_{X}^{♭} ).  (4.12)

An abstract interpretation of D_{H_{X}}^{♭} can be specified by choosing an abstract domain D_{H_{X}}^{♯} and a suitable abstraction function

α: D_{H_{X}}^{♭} → D_{H_{X}}^{♯} .  (4.13)

If D_{H_{X}}^{♯} is able to encode enough structural (Herbrand) information from D_{H_{X}}^{♭} so as to achieve the desired precision, fine. If this is not the case, it is possible to improve the situation by keeping some structural information explicit.

One way of doing that is to perform a change of representation for D_{H_{X}}^{♭}, which is the basis for further abstractions. The new representation is obtained by factoring out some common Herbrand information. The meaning of ‘some’ is encoded by a function.

Definition 60 (Common anti-instance function.) Any function

ϕ: ⋃_{n∈ℕ}℘(T_{z}^{n}) → T_{z′}^{⋆}

is called a common anti-instance function if it satisfies, for each n ∈ ℕ and each T̂ ∈ ℘(T_{z}^{n}):

1. ϕ(T̂) ∈ T_{z′}^{n};

2. if ϕ(T̂) = r̄ and |vars(r̄)| = m with m ≥ 0, then

∀t̄∈ T̂ : ∃ū ∈ T_{z}^{m} . r̄[ū/ vseq(r̄)] = t̄.

In words, ϕ(T̂) is an anti-instance, in z′-form, of each t̄∈ T̂. Any choice of ϕ induces a function

Φ_{ϕ}: D_{H_{X}}^{♭} → T_{z}^{⋆} × ℘(T_{z}^{⋆} × D_{X}^{♭} ),  (4.14)

which is given, for each Ê^{♭} ∈ D_{H_{X}}^{♭} by

Φ_{ϕ}(Ê^{♭}) =^{def} (s̄′, { (ū, D^{♭}) | (t̄, D^{♭}) ∈ Ê^{♭}, s̄[ū/vseq(s̄)] = t̄}) ,

(4.15)

where s̄ =^{def} ϕ(π_{1}(Ê^{♭})). The corestriction to the image of Φ_{ϕ}, that is Φ_{ϕ}: D_{H_{X}}^{♭} → Φ_{ϕ}(D_{H_{X}}^{♭} ), is an isomorphism, the inverse being

Φ_{ϕ}^{−1}((s̄, F̂^{♭})) =^{def} { (s̄′[ū/vseq(s̄′)], D^{♭}) | (ū, D^{♭}) ∈ F̂^{♭} }. (4.16)

So far, we have just chosen a different representation for D_{H_{X}}^{♭}, that is Φ_{ϕ}(D_{H_{X}}^{♭} ). The idea behind structural information analysis is to leave the first component (also called the pattern component)

[figure: ℘(T_{z}^{⋆} × D_{X}^{♭} ); α; Φ_{ϕ}; D_{H_{X}}^{♯}; T_{z}^{⋆} × ℘(T_{z}^{⋆} × D_{X}^{♭} ); (id,α); T_{z}^{⋆} × D_{H_{X}}^{♯}; α′]

Figure 4.4: Upgrading a domain with structural information.

of the new representation untouched, while abstracting the second component by means of α, as illustrated in Figure 4.4. The dotted arrow indicates a residual abstraction function α′. As we will see in Section 4.5.4, such a function is implicitly required in order to define an important operation over the new abstract domain T_{z}^{⋆} × D_{H_{X}}^{♯}. Notice that α′ may or may not make the diagram of Figure 4.4 commute (although often α′ turns out to have this property).

This approach has several advantages. First of all, factoring out common structural information improves the analysis precision, since part of the approximated k-tuples of terms is recorded, in concrete form, into the first component of T_{z}^{⋆} × D_{H_{X}}^{♯}. Secondly, the above construction is adjustable by means of the parameter ϕ. The most precise choice consists in taking ϕ to be a least common anti-instance function. For example, the set

Ŝ =^{def} {⟨(s(0), c(Z_{1}, nil)), C_{1}⟩, ⟨(s(s(0)), c(Z_{1}, c(Z_{2}, nil))), C_{2}⟩},

where C_{1}, C_{2} ∈ D_{X}^{♭}, is mapped by the Φ_{lca} function onto

Φ_{lca}(Ŝ) = ((s(Z_{1}), c(Z_{2}, Z_{3})),

{⟨(0, Z_{1}, nil), C_{1}⟩, ⟨(s(0), Z_{1}, c(Z_{2}, nil)), C_{2}⟩}).

At the other side of the spectrum is the possibility of choosing ϕ so that it returns a k-tuples of distinct, new variables for each set of k-tuples of terms. This correspond to a framework where structural information is just discarded. With this choice, Ŝ would be mapped onto

((Z_{1}, Z_{2}), Ŝ).

In-between these two extremes there are a number of possibilities that help managing the complexity/precision tradeoff. The k-tuples returned by ϕ can be limited in depth [ST84, MS88], for instance. More useful is to limit them in width, that is, limiting the number of symbols’ occurrences. This flexibility allows to design the analysis’ domains without caring about structural information: the problem is always to approximate elements of ℘(T_{z}^{a} × D_{X}^{♭} ). Whether a is fixed by the arity of a predicate or a is the number of variables occurring in some pattern does not really matter.

4.4 Parametric Structural Analysis

In order to specify the abstract domain for the analysis, we need some assumptions on the concrete X domain D_{X}^{♭}, which represents the X -part of consistent constraint stores. One can think about D_{X}^{♭} as made up of first-order sentences: the techniques described in Chapter 3 come in handy for this purpose. In this view, the operator ⊗: D_{X}^{♭} × D_{X}^{♭} → D_{X}^{♭} corresponds to logical conjunction. Moreover, we assume that it makes sense to talk about the free variables of D^{♭} ∈ D_{X}^{♭}, denoted by FV (D^{♭}). Let s̄, t̄, ū ∈ T_{z}^{⋆} and D^{♭}, E^{♭} ∈ D_{X}^{♭} such that FV (D^{♭}) ⊆ vars(t̄). When we write (ū, E^{♭}) = ϱs̄((t̄, D^{♭})), we mean that ū = ϱs̄(t̄) and that E^{♭} has been obtained from D^{♭} by applying the same renaming applied to t̄ in order to obtain ū.

Another natural thing to do is projecting a satisfiable store: thus ∃V̄ D^{♭}, where V̄ is a tuple (or set) of variables, is assumed to be as defined.

The last thing we need is the ability of adding an equality constraint to a constraint store. Thus D^{♭}[t_{1} = t_{2}] is the store obtained from D^{♭} by injecting the equation t_{1} = t_{2}, provided that the resulting store is consistent, otherwise the operation is undefined. Notice that we assume D_{X}^{♭} and its operations encode both the proper X - solver and the so called interface between the Herbrand engine and the X -solver [JM94]. In particular, the interface is responsible for type-checking of the equations it receives. For example, in CLP(R) the interface is responsible for the fact that X = a cannot be consistently added to a constraint store where X was previously classified as numeric.

We now turn our attention to the abstract domain that is the parameter of the generic structural domain. We will denote it simply by D^{♯}, instead of D_{H_{X}}^{♯}. Thus, assuming that X has been fixed, D_{X}^{♭} is indicated just by D^{♭}.

Since the aim here is maximum generality, we refer to a very weak abstract interpretation framework.

Definition 61 (Abstract domain.) An abstract domain for H_{X} is any set D^{♯} equipped with a preorder relation ⊑, an order preserving function

γ: D^{♯} → D^{♭},

an upper-bound operator

⊕: D^{♯} × D^{♯} → D^{♯},

and a least element ⊥^{♯} such that γ(⊥^{♯}) = ∅.

Thus, for each D_{1}^{♯}, D_{2}^{♯} ∈ D^{♯}, we have both

D_{1}^{♯} ⊑ D_{2}^{♯} ⟹ γ(D_{1}^{♯}) ⊆ γ(D_{2}^{♯}),  (4.17)

and

γ(D_{1}^{♯} ⊕ D_{2}^{♯}) ⊇ γ(D_{1}^{♯}) ∪ γ(D_{2}^{♯}).  (4.18)

The structural information construction upgrades any given abstract domain D^{♯} as follows.

Definition 62 (The Pattern(·) construction.) Let D^{♯} be an abstract domain. Then

Pattern(D^{♯}) =^{def} { (s̄, D^{♯}) ∈ T_{z}^{⋆} × D^{♯} | γ(D^{♯}) ⊆ T_{z}^{|vars(s̄)|} × D^{♭} }.

The meaning of each element (s̄, D) ∈ Pattern(D^{♯}) is given by γ: Pattern(D^{♯}) → ℘(T_{z}^{⋆} × D^{♭}):

γ((s̄, D^{♯})) =^{def} { (s̄′[ū/ vseq(s̄′)], D^{♭}) | (ū, D^{♭}) ∈ γ(D^{♯}) }.

The following result is needed for what follows. It is a straight consequence of Definition 62.

Proposition 63 Whenever (s̄, D_{1}^{♯}), (s̄, D_{2}^{♯}) ∈ Pattern(D^{♯})

γ(D_{1}^{♯}) ⊆ γ(D_{2}^{♯}) ⟹ γ((s̄, D_{1}^{♯})) ⊆ γ((s̄, D_{2}^{♯})).

4.5 Operations for the Analysis

In this section we define the operations over Pattern(D^{♯}) that are needed for the analysis in a bottom-up framework. In order of appearance into the analysis process:

• we need an operation that takes two descriptions and, roughly speaking, juxtaposes them. This operation, which we call meet with renaming apart, is needed when “solving” a clause body with respect to the current interpretation.

• Unification, that realizes “parameter passing”. The descriptions that were simply juxtaposed are thus made to communicate with each other.

• When all the goals in a clause body have been solved, projection is used to restrict the abstract description to the tuple of arguments of the clause’s head.

• The operation of remapping is used to adapt a description to a different, less precise, pattern component. It is used in order to realize various join and widening operations.

• The join operation is parameterized with respect to a common anti-instance function. It is used to merge descriptions arising from the different sets of computation paths explored during the analysis.

• The comparison operation is employed by the analyzer in order to check whether a local (to a program clause or predicate) fixpoint has been reached.

The above operations over Pattern(D^{♯}) induce the need for other operations on the underlying domain D^{♯}. The latter are specified in the next sections so that the correctness of the analysis can be ensured.

4.5.1 Meet with Renaming Apart

This operation is very simple.

Definition 64 (The rmeet operation.) Let ▷: D^{♯} × D^{♯} → D^{♯} be such that, for each D_{1}^{♯}, D_{2}^{♯} ∈ D^{♯},

γ(D_{1}^{♯} ▷ D_{2}^{♯}) =

\{ (r̄_{1} :: w̄_{2}, D_{1}^{♭} ⊗ E_{2}^{♭}) | \begin{matrix}(r̄_{1}, D_{1}^{♭}) ∈ γ(D_{1}^{♯}) \\ (r̄_{2}, D_{2}^{♭}) ∈ γ(D_{2}^{♯}) \\ (w̄_{2}, E_{2}^{♭}) = ϱr̄_{1}((r̄_{2}, D_{2}^{♭})) \end{matrix} \}.

Then, for each (s̄_{1}, D_{1}^{♯}), (s̄_{2}, D_{2}^{♯}) ∈ Pattern(D^{♯}).

rmeet((s̄_{1}, D_{1}^{♯}), (s̄_{2}, D_{2}^{♯})) =^{def} (s̄_{1} :: ϱs̄_{1}(s̄_{2}), D_{1}^{♯} ▷ D_{2}^{♯}).

The following result is a direct consequence of the definition: there is no precision loss in rmeet.

Theorem 65 For each (s̄_{1}, D_{1}^{♯}), (s̄_{2}, D_{2}^{♯}) ∈ Pattern(D^{♯}).

γ(rmeet((s̄_{1}, D_{1}^{♯}), (s̄_{2}, D_{2}^{♯})))

= \{ (t̄_{1} :: ū_{2}, D_{1}^{♭} ⊗ E_{2}^{♭}) | \begin{matrix}(t̄_{1}, D_{1}^{♭}) ∈ γ((s̄_{1}, D_{1}^{♯})) \\ (t̄_{2}, D_{2}^{♭}) ∈ γ((s̄_{2}, D_{2}^{♯})) \\ (ū_{2}, E_{2}^{♭}) = ϱt̄_{1}((t̄_{2}, D_{2}^{♭})) \end{matrix} \}.

4.5.2 Unification with Occur-Check

In this section we assume that the execution mechanism of the language being analyzed performs unifications without omitting the occur-check. With this hypothesis (which, unfortunately, is seldom verified) we can easily complete the unification algorithm given in [CLV93]. When the occur-check fails in the abstract unification we know that the computation path being analyzed can be safely pruned, because the concrete unification would have failed at that point, if not before. Notice that, for the purpose of the present discussion, the occur-check need not be implemented explicitly, that is by making the unification fail in the logic programming sense. Since our data-flow analyses provide information of the kind

if control gets to this point, then that will hold there,

a more drastic handling of the occur-check is acceptable. If we are guaranteed that the concrete system enters either an error state or an infinite loop whenever a cyclic binding is attempted, then the abstract unification procedure presented in this section can safely be used. We will discuss later what can be done for those systems where the occur-check is, by any means, omitted.

We start with a description (s̄, D^{♯}) ∈ Pattern(D^{♯}) and two terms to be unified, t and u, such that

vars((t, u)) ⊆ vars(s̄).

We then apply the procedure unify, given as Algorithm 1, to s̄, D^{♯}, t and u.

In the macro-operation bind(s̄, D^{♯}, u, Z_{h}), s̄ is passed only in order to maintain the connection between the variables in (u, Z_{h}) and the description D^{♯}. We assume, without loss of generality, that whenever bind(s̄, D^{♯}, u, Z_{h}) is invoked we have

|vars(s̄)| = m, with m ≥ 0.

The result of the operation will be a description D_{1}^{♯} such that γ(D_{1}^{♯}) ⊆ T_{z}^{m−1} × D^{♭}. This is because, after the binding, Z_{h} will not be referenced anymore. What remains to be described is the operation of reflecting the binding of Z_{h} to u into D^{♯} so to obtain D_{1}^{♯}. We will denote this operation by D^{♯}[u/Z_{h}], and present its variants (depending on whether u is a constant or a number or a variable or a compound term) in the next sections. procedure unify(s̄, D^{♯}, t, u) 1: if t ≠ u then 2: if t = f(t_{1}, …, t_{n}) and u = f(u_{1}, …, u_{n}) then 3: for all i = 1, …, n do 4: unify(s̄, D^{♯}, t_{i}, u_{i}) 5: else if t = Z_{h} then 6: if Z_{h} does not occur in u then 7: D^{♯} := bind(s̄, D^{♯}, u, Z_{h}) {invokes underlying domain} 8: Z_{h} := u {instantiates all the occurrences of Z_{h}} 9: s̄:= η(s̄) {normalization} 10: else 11: D^{♯} := ⊥^{♯} 12: else if u = Z_{k} then 13: unify(s̄, D^{♯}, u, t) 14: else 15: D^{♯} := ⊥^{♯} Algorithm 1: Unification for the parametric structural domain.

Binding to a Constant or a Number

The result of D^{♯}[k/Z_{h}], where k is a symbolic constant or a number and h ∈ {1, …, m} is any D_{1}^{♯} ∈ D^{♯} such that

γ(D_{1}^{♯}) ⊇ { ((t_{1}, …, t_{h−1}, t_{h+1}, …, t_{m}), D^{♭}[t_{h} = k])

| ((t_{1}, …, t_{m}), D^{♭}) ∈ γ(D^{♯}) }.

Notice that

vseq(s̄[k/Z_{h}]) = (Z_{1}, …, Z_{h−1}, Z_{h+1}, …, Z_{m}).

Similar comments apply also to what follows.

Binding to an Alias

Here we must specify an admissible result, D_{1}^{♯}, for the operation D^{♯}[Z_{i}/Z_{h}] with i, h ∈ {1, …, m} and i ≠ h. In order to reduce the complexity of the definition we need some special notation for sequences.

Let U be a set. We define the operation · \ ·: U^{⋆} × ℘_{f}(U) → U^{⋆} as follows. For each sequence L ∈ U^{⋆} and each set S ∈ ℘_{f}(U), the sequence L\U is obtained by removing from L all the elements that appear in U. Formally,

ε \ S =^{def} ε;
((x) :: L) \ S =^{def} \{ \begin{matrix}L \ S, & if x ∈ S; \\ (x) :: (L \ S), & if x ∉ S. \end{matrix}

Let us define τ(k): {1, …, m − 1} → {1, …, m} as

τ(k) =^{def} π_{k}((1, …, h − 1) :: ((i) \ {1, …, h − 1})

:: ((h + 1, …, m) \ {i})).

The transformation τ is such that, for each k = 1, …, m − 1,

π_{k}(vseq(s̄[Z_{i}/Z_{h}])) = Z_{τ(k)}.

Now, D_{1}^{♯} must satisfy

γ(D_{1}^{♯}) ⊇ { ((π_{τ(1)}(t̄), …, π_{τ(m−1)}(t̄)), D^{♭}[π_{h}(t̄) = π_{i}(t̄)])

| (t̄, D^{♭}) ∈ γ(D^{♯}) }.

Binding to a Compound

Specifying the result of the operation D^{♯}[u/Z_{h}] is only slightly more complicated. Suppose that

vseq(u) = (Z_{j_{1}}, …, Z_{j_{l}}),

so that Z_{h} ∉ {Z_{j_{1}}, …, Z_{j_{l}}} and the transformation τ is given by

τ(k) =^{def} π_{k}((1, …, h − 1)

:: ((j_{1}, …, j_{l}) \ {1, …, h − 1})

:: ((h + 1, …, m) \ {j_{1}, …, j_{l}})). (4.19)

Then D^{♯}[u/Z_{h}] is allowed to return any D_{1}^{♯} such that

γ(D_{1}^{♯}) ⊇ \{ ((π_{τ(k)}(t̄))_{k=1}^{m−1}, D_{1}^{♭}) | \begin{matrix}(t̄, D^{♭}) ∈ γ(D^{♯}) \\ θ = [u_{j_{1}}/Z_{j_{1}}, …, u_{j_{l}}/Z_{j_{l}}] \\ π_{h}(t̄) = uθ \\ D_{1}^{♭} = D^{♭}[π_{j_{1}}(t̄) = u_{j_{1}}, \\ …, π_{j_{l}}(t̄) = u_{j_{l}}] \end{matrix} \}.

The proof of the overall correctness of Algorithm 1 is rather tedious and thus omitted. As observed in [CLV93] it can be obtained by systematic generalization of the proof given in Musumbu’s PhD thesis [Mus90].

4.5.3 Projection

This operation consists simply in dropping a suffix of the pattern component, with the consequent projection on the underlying domain.

Definition 66 (The project operation.) Let

{¯∃ ; ¯∃_{i}: D^{♯} → D^{♯}}_{i∈ℕ}

be a family of operations such that, for each m ∈ ℕ, each D^{♯} ∈ D^{♯} with γ(D^{♯}) ⊆ T_{z}^{m} × D^{♭}, and for each j < m,

γ(¯∃ ; ¯∃_{j} D^{♯}) ⊇ \{ (r̄, E^{♭}) | \begin{matrix}(ū, D^{♭}) ∈ γ(D^{♯}) \\ r̄ = (π_{1}(ū), …, π_{j}(ū)) \\ Δ = \overline{vars(r̄)} \\ E^{♭} = ∃_{Δ}D^{♭} \end{matrix} \}.

Then, for each (s̄, D^{♯}) ∈ Pattern(D^{♯}) such that s̄ ∈ T_{z}^{n} and for each k < n,

project_{k}((s̄, D^{♯})) =^{def} (\underbrace{(π_{1}(s̄), …, π_{k}(s̄))}_{t̄}, ¯∃ ; ¯∃_{j} D^{♯}),

where j =^{def} |vars(t̄)|.

It is easy to show that project is indeed correct with respect to the obvious concrete operation.

4.5.4 Remapping

Consider a description (s̄, D♯ ; s̄) ∈ Pattern(D^{♯}) and a pattern r̄ ∈ T_{z′}^{⋆} such that r̄ is an anti-instance of s̄. We want to obtain D♯ ; r̄ ∈ D^{♯} such that

γ((r̄′, D♯ ; r̄)) ⊇ γ((s̄, D♯ ; s̄)).  (4.20)

This is what we call remapping (s̄, D♯ ; s̄) to r̄.

Definition 67 (The remap operation.) Let (s̄, D♯ ; s̄) be a description with s̄ ∈ T_{z}^{k} and let r̄ ∈ T_{z′}^{k} be an anti-instance of s̄. Assume |vars(r̄)| = m and let ū ∈ T_{z}^{m} be the unique tuple such that

r̄[ū/ vseq(r̄)] = s̄.  (4.21)

Then the operation remap(s̄, D♯ ; s̄, r̄) yields D♯ ; r̄ such that

γ(D♯ ; r̄) ⊇ { (ū′[t̄/ vseq(ū′)], D^{♭}) | (t̄, D^{♭}) ∈ γ(D♯ ; s̄) }.  (4.22)

Observe that the remap function is closely related to the residual abstraction function α′ of Figure 4.4. We now prove that the specification of remap meets our original requirement.

Theorem 68 Let (s̄, D♯ ; s̄) be a description with s̄ ∈ T_{z}^{k}. Let also r̄ ∈ T_{z′}^{k} be an anti-instance of s̄. If

D♯ ; r̄ = remap(s̄, D♯ ; s̄, r̄)

then

γ((r̄′, D♯ ; r̄)) ⊇ γ((s̄, D♯ ; s̄)).

Proof Let us assume the hypotheses of Definition 67. By Definition 62, γ((r̄, D♯ ; r̄)) is given by

{ (r̄[w̄/ vseq(r̄)], D^{♭}) | (w̄, D^{♭}) ∈ γ(D♯ ; r̄) }.  (4.23)

Let us define

A =^{def} { (ū′[t̄/ vseq(ū′)], D^{♭}) | (t̄, D^{♭}) ∈ γ(D♯ ; s̄) }.

By (4.22) we have γ(D♯ ; r̄) ⊇ A. Thus, by Proposition 63, the set (4.23) contains

{ (r̄[w̄/ vseq(r̄)], D^{♭}) | (w̄, D^{♭}) ∈ A }.

This can be rewritten as

{ (r̄[ū/ vseq(r̄)])′[t̄/ vseq(ū′)], D^{♭}) | (t̄, D^{♭}) ∈ γ(D♯ ; s̄) },

which, by (4.21), is equivalent to

{ s̄′[t̄/ vseq(s̄′)], D^{♭}) | (t̄, D^{♭}) ∈ γ(D♯ ; s̄) },

since vseq(ū) = vseq(s̄). What we have just written is the definition

of γ((s̄, D♯ ; s̄)). □

4.5.5 Join and Widenings

The operation of merging two descriptions turns out to be an easy one, once remapping has been defined. Let (s̄_{1}, D_{1}^{♯}) and (s̄_{2}, D_{2}^{♯}) be two descriptions with s̄_{1}, s̄_{2} ∈ T_{z}^{k}. The resulting description is (r̄′, E_{1}^{♯} ⊕ E_{2}^{♯}), where r̄ ∈ T_{z′}^{k} is an anti-instance of both s̄_{1} and s̄_{2} and

E_{i}^{♯} = remap(s̄_{i}, D_{i}^{♯}, r̄), for i = 1, 2.

We note again that r̄ might be the least common anti-instance of s̄_{1} and s̄_{2}, or it can be a further approximation of lca{s̄_{1}, s̄_{2}}: this is one of the degrees of freedom of the framework.

Definition 69 (The join_{ϕ} operations.) Let ϕ be any common anti-instance function. The operation (partial function)

join_{ϕ}: ℘_{f}(Pattern(D^{♯})) ↣ Pattern(D^{♯})

is defined as follows. For each k ∈ ℕ and each finite family

F =^{def} {(s̄_{i}, D_{i}^{♯})}_{i∈I}

of elements of Pattern(D^{♯}) such that s̄_{i} ∈ T_{z}^{k} for each i ∈ I,

join_{ϕ}(F) =^{def} (r̄′, ⨁_{i∈I} remap(s̄_{i}, D_{i}^{♯}, r̄)),

where

r̄ =^{def} ϕ({s̄_{i}}_{i∈I}).

Theorem 70 Let F be as in Definition 69. For each common antiinstance function ϕ and each (s̄_{j}, D_{j}^{♯}) ∈ F,

γ(join_{ϕ}(F)) ⊇ γ((s̄_{j}, D_{j}^{♯})).

Proof Let j ∈ I and r̄ =^{def} ϕ({s̄_{i}}_{i∈I}). Then r̄ is an anti-instance of s̄_{j}. Thus

γ((r̄, ⨁_{i∈I} remap(s̄_{i}, D_{i}^{♯}, r̄))) ⊇ γ((r̄, remap(s̄_{j}, D_{j}^{♯}, r̄))

[by Def. 61 and Prop. 63]

⊇ γ((s̄_{j}, D_{j}^{♯}))

[by Thm. 68]

□

As far as widening operators are concerned, there are several possibilities. First of all, we might want to distinguish between widening in the pattern component and widening on the underlying domain. The former can be defined as any join operation join_{ϕ} with ϕ different from lca. The latter consists in propagating the widening to the underlying domain. For instance, the following widening operator is the default one applied by China:

widen((s̄_{1}, D_{1}^{♯}), (s̄_{2}, D_{2}^{♯})) =^{def} \{ \begin{matrix}(s̄_{2}, D_{2}^{♯}), & if s̄_{1} ≠ s̄_{2}; \\ (s̄_{2}, D_{1}^{♯} ∇ D_{2}^{♯}), & if s̄_{1} = s̄_{2}. \end{matrix}(4.24)

This operator refrains from widening unless the pattern component is stabilized (see the next section to see why it works).

More drastic widenings can be defined. For example, we can exploit the fact that widenings need to be evaluated over (s̄_{1}, D_{1}^{♯}) and (s̄_{2}, D_{2}^{♯}) only when s̄′_{2} is an anti-instance of s̄_{1}. This happens because (s̄_{1}, D_{1}^{♯}) is the result of the previous iteration, whereas (s̄_{2}, D_{2}^{♯}) has been obtained at the current iteration from a join operation that included (s̄_{1}, D_{1}^{♯}). Thus another possibility is given by

widen′_{ϕ}((s̄_{1}, D_{1}^{♯}), (s̄_{2}, D_{2}^{♯})) =^{def} (s̄_{2}, remap(s̄_{1}, D_{1}^{♯}, s̄′_{2}) ∇ D_{2}^{♯}). (4.25)

It is straightforward to show that (4.24) and (4.25) are correct. Other operators can be defined by using joins and remappings (and don’t forget that the underlying domain may have a variety of widening operators to choose from).

4.5.6 Comparing Descriptions

The last operation that is needed in order to put Pattern(D^{♯}) at work is for comparing descriptions.

Definition 71 (Approximation ordering.) The approximation ordering over Pattern(D^{♯}), denoted by ⊑, is defined as follows, for each (s̄_{1}, D_{1}^{♯}), (s̄_{2}, D_{2}^{♯}) ∈ Pattern(D^{♯}):

(s̄_{1}, D_{1}^{♯}) ⊑ (s̄_{2}, D_{2}^{♯}) ⟺^{def} s̄_{1} = s̄_{2} ∧ D_{1}^{♯} ⊑ D_{2}^{♯}.

It must be stressed that the above approximation ordering is also “approximate”, since it does not take into account the peculiarities of D^{♯}. More refined orderings can be obtained in a domaindependent way, namely, when D^{♯} has been fixed.

The following result is a trivial consequence of Definition 61 and Proposition 63.

Theorem 72 If (s̄_{1}, D_{1}^{♯}) ⊑ (s̄_{2}, D_{2}^{♯}) then γ((s̄_{1}, D_{1}^{♯})) ⊆ ((s̄_{2}, D_{2}^{♯})). Moreover, ⊑ is a preorder over Pattern(D^{♯}).

Observe that the ability of comparing descriptions only when they have the same pattern is not restrictive in a data-flow analysis setting. The analyzer, in fact, will only need to compare the descriptions arising from the iteration sequence at two consecutive steps. Moreover, if we denote by (s̄_{n}, D_{n}^{♯}) the description at step n, we have

(s̄_{i+1}, D_{i+1}^{♯}) = widen((s̄_{i}, D_{i}^{♯}), join_{ϕ}({(s̄_{i}, D_{i}^{♯}), … })), (4.26)

where the widening is possibly omitted. Whether or not the widening has been applied, this implies that s̄′_{i+1} is an anti-instance of s̄_{i} and

γ((s̄_{i}, D_{i}^{♯})) ⊆ γ((s̄_{i+1}, D_{i+1}^{♯})).  (4.27)

If also the reverse inclusion holds in (4.27) then we have reached a local fixpoint. The analyzer uses the approximate ordering in order to check for this possibility. Namely, it asks whether

(s̄_{i+1}, D_{i+1}^{♯}) ⊑ (s̄_{i}, D_{i}^{♯}).  (4.28)

The approximate test, of course, can fail even when equality does hold in (4.27). But this will be a fault of the pattern component only a finite number of times, since s̄_{i+1} is an anti-instance of s̄_{i} and T_{z}^{k}, ordered by the anti-instance relation, has finite height. Thus, there exists ℓ ∈ ℕ such that, for each i ≥ ℓ, s̄_{i} = s̄_{ℓ}. After the ℓ-th step the accuracy of the approximate ordering is in the hands of D^{♯}.

4.6 What if the Occur-Check is Omitted?

Suppose that the analyzed language omits the occur-check, as it is often the case. It has been known since [Col84] that terminating unification procedures without the occur-check solve term equations on the domain of rational trees. The first question is: are the abstract domains and techniques we all know and use still correct for such languages?

For a precise answer we ought to consider each such domain and the corresponding abstract operations, and prove their correctness with respect to the “new” concrete domain. To our surprise, we were not able to find in the literature any reference to this issue, not even in papers dealing with the analysis of Prolog (which, proverbially, omits the occur-check). However, we still hope that this (potentially devastating) problem has not escaped the attention of researchers.

A partial, tentative answer, is that it should not be difficult to prove the correctness, over rational trees and rational unification, of those domains that depend only on the set of variables occurring in terms: the typical domains for groundness and sharing without linearity, for instance. If you only observe the set of variables’ occurrences, then a finite term cannot be distinguished from a rational one. The infinite term f(f(f(⋯))), the rational solution of the equation X = f(X), does not contain any variable and thus is ground. In the rational solution of X = f(X, Y), instead, only the variable Y occurs. If X denotes a ground rational tree then also Y denotes a ground rational tree, and the converse holds too. So, as long as you abstract a concrete equation of the form X = t with the ground dependency

X ↔ ⋀(vars(t) \ {X}),

the usual domains for groundness should remain correct also when the occur-check is omitted in the analyzed language. (See Chapter 6 on the subject of groundness analysis.) This implicit pattern of ignoring the variables’ occurrences that are responsible for the introduction of a cycle works whenever only the set of variables occurring in (infinite) terms is relevant. This is because, if t̃ is the most general rational unifier of X = t, with X occurring in t, we can write

vars(t̃) = (vars(t) \ {X}) ∪ vars(t̃).  (4.29)

The least solution of (4.29) is clearly vars(t̃) = vars(t)\{X}. Notice that this pattern does not work when the multiplicity of occurrences is relevant, as we will now see.

For a finite term t we say that t is linear if and only if t does not contain multiple occurrences of any variable. The linearity property is not very interesting in itself, but it allows us to improve sharing analyses. When two terms t and u are unified, knowing that t is linear allows one to conclude that any two variables in u that did not share before the unification, will not share after the unification. The notion of linearity can easily be extended to rational trees, but care must be taken. When abstracting the constraint X = f(X, Y), for instance, we cannot ignore the occurrence of X in the right-hand side. For a very good reason: this occurrence is responsible for the fact that the result of the unification is a non-linear term. Indeed, the resulting rational tree has an infinite number of occurrences of Y, as shown in Figure 4.5. Failure to recognize this fact leads to an unsound sharing analysis: if t̃ is the most general rational unifier of X and f(X, Y), unifying f(f( , A), B) with t̃ makes A and B share, as expected.

This is not the place for reconsidering all the domains that have been studied for the analysis of (constraint) logic programs. We do believe that most of them can be easily fixed in order to obtain safe approximations of the concrete domains based on rational trees. So, let us simply assume we have some domain D^{♯} that is a correct

[figure: f; f; Y; Y]

Figure 4.5: The rational tree that solves X = f(X, Y).

abstraction of a domain of extended rational trees. Can we extend the parametric structural construction so that we can apply it to D^{♯} and still obtain a correct analysis?

When the analyzed language does not perform the occur-check we can no longer fail when a cyclic binding is attempted in the pattern component: something else has to be done. Notice that this problem cannot be escaped easily. Even the unreasonable hypothesis that one deals only with occur-check free programs^{3} does not prevent an analyzer employing Pat(ℜ) or Pattern(D^{♯}) from trying to build a cyclic pattern (because the analysis was not strong enough to detect an impossible computation path).

The obvious way to go is to have a tuple of rational trees, instead of finite terms, in the pattern component with all the consequent modifications. This, however, might be undesirable because

1. it is more expensive: all the tree visits must employ some marking technique in order to avoid infinite loops; computing common anti-instances is more difficult than in the finite case; the same holds for detecting when two rational trees are variant of each other; garbage collection is more complicated and so on;

3Note that occur-check freedom is an undecidable program property.

2. cyclic bindings arise in the analysis of very few programs.^{4}

The unlikeliness of the phenomenon, while irrelevant as far as the soundness issues are concerned, seems to discourage the adoption in practice of rational trees for the pattern component: very few programs can benefit from the little extra precision so gained, at a comparatively high cost. We thus prefer leaving the pattern component as it is now: a tuple of finite terms. But, what do we do in the case of cyclic bindings?

If we restrict ourselves to domains expressing monotonic properties^{5}, then ignoring the bindings that would cause the introduction of a cycle is a safe (though imprecise) way out: throwing away information is always correct for these domains. In this particular case, lines 9 and 10 of Algorithm 1 on page 139 could simply be removed. This is not true for non-monotonic properties: the cyclic bindings cannot be ignored altogether. As previously noted, omitting communication of the cyclic bindings to the underlying domain D^{♯} implies, in general, loosing soundness. Let us consider the following simple program:

p(X, Y) :-
    Z = f(Z, X, Y),
    Z = f(_, A, A).

Most Prolog and CLP systems give the answer X = Y to the query ?- p(X, Y). Consequently, any non-trivial sharing analysis that ignores the cyclic binding is going to obtain wrong results. procedure unify(s̄, D^{♯}, t, u) if t ≠ u then

4Only one out of more than a hundred third-party programs in the test-suite of China used to provoke a cyclic binding during the analysis. This happened with an early version that did not take some built-ins into account. With the current version this does not happen anymore.

5Properties that are preserved as concrete computations progress.

if t = f(t_{1}, …, t_{n}) and u = f(u_{1}, …, u_{n}) then

for all i = 1, …, n do unify(s̄, D^{♯}, t_{i}, u_{i})

else if t = Z_{h} then

if Z_{h} does not occur in u then D^{♯} := bind(s̄, D^{♯}, u, Z_{h}) {invokes underlying domain} Z_{h} := u {instantiates all the occurrences of Z_{h}}

else D^{♯} := cyclic(s̄, D^{♯}, u, Z_{h}) {underlying domain} Z_{h} := u[Z_{m+1}/Z_{h}] {cycle is broken: m = |vars(s̄)|}

s̄:= η(s̄) {normalization} else if u = Z_{k} then unify(s̄, D^{♯}, u, t)

else

D^{♯} := ⊥^{♯} Algorithm 2: Revised unification for the structural domain.

4.6.1 Unification without Occur-Check

The above discussion motivates the introduction of a revised unification algorithm, given as Algorithm 2, where we take into account the cyclic bindings both in the pattern component and the underlying domain. Notice how the cyclic binding is avoided in the pattern component: by substituting the dangerous occurrences of Z_{h} with a new variable. This, of course, is more precise than omitting the binding in the pattern component.

Cyclic Binding to a Compound

The operation cyclic(s̄, D^{♯}, u, Z_{h}) must now be specified. We assume again that |vars(s̄)| = m, with m ≥ 0. The result of the operation will be a description D_{1}^{♯} such that

γ̃(D_{1}^{♯}) ⊆ T̃_{z}^{m} × D^{♭},

where T̃_{z} denotes the set of all normalized rational trees and γ̃ is the new concretization function. Notice that D_{1}^{♯} denotes an mtuple of rational trees: Z_{h} will not be referenced anymore, but a new variable Z_{m+1} has been introduced.

The following definition is reproduced from [Kei94].

Definition 73 (Rational solved form.) [Col82, Col84] A set of equation is circular if it has the form

{ X_{1} = X_{2}, ; X_{2} = X_{3}, ; ⋮ ; X_{n−1} = X_{n}, ; X_{n} = X_{1} } .

A rational solved form is a conjunction of equations of the form

{ X_{1} = t_{1}, ; X_{2} = t_{2}, ; ⋮ ; X_{n} = t_{n} }

not containing any circular subset.

Each rational solved form corresponds to a tuple of rational trees. Conversely, each rational tree can be associated to rational solved form in which “the root” has been identified. Let t̃ be a rational tree, and let u be a fixed, infinite set of special variables, disjoint from any other set of variables we have mentioned so far. When U is a variable not occurring in t̃, we will denote by eq_{U}(t̃) a rational solved form R such that

1. if lhs(R) and rhs(R) denote the set of variables on the lefthand sides of the equations in R and the set of terms occurring in the right-hand sides, respectively, then

lhs(R) ∩ vars(rhs(R)) ⊂ u;

2. U describes t̃ in R.

For instance, if t̃ is the rational tree depicted in Figure 4.5 then eq_{U}(t̃) = {U = f(U, Y)}.

Let us define τ(k): {1, …, m} → {1, …, m} exactly as we did in (4.19) on page 141 (but note that the domain has changed). Then D_{1}^{♯} must satisfy

γ(D_{1}^{♯}) ⊇ \{ ((π_{τ(k)}(t̄))_{k=1}^{m}, D_{1}^{♭}) | \begin{matrix}(t̄, D^{♭}) ∈ γ̃(D^{♯}) \\ \{Z_{j_{1}} = u_{j_{1}}, …, Z_{j_{l}} = u_{j_{l}}, … \} \\ is a r.s.f. (if any) of \\ eq_{U}((π_{h}(t̄)) ∪ \{U = u\} \\ D_{1}^{♭} = D^{♭}[π_{j_{1}}(t̄) = u_{j_{1}}, \\ …, π_{j_{l}}(t̄) = u_{j_{l}}] \end{matrix} \},

where U ∈ u.

4.7 Conclusion

We have presented the rational construction of a generic domain for structural analysis of CLP(H_{X}) languages: Pattern(D_{H_{X}}^{♯}), where the parameter D_{H_{X}}^{♯} is an abstract domain satisfying certain properties, We build on the parameterized Pat(ℜ) domain of Cortesi et al. [CLV93, CLV94], which is restricted to logic programs. However, while Pat(ℜ) is presented as a specific implementation of a generic structural domain, our formalization is independent from specific implementation techniques. Reasoning at a higher level of abstraction we are able to fully justify the ideas behind the structural domain. In particular, appealing to familiar notions of unification theory, we can identify an important parameter (a common anti-instance function, missing in [CLV93]) that gives some control over the precision and computational cost of the resulting generic structural domain.

In addition, we have corrected an oversight in the work of Cortesi et al.While they assume explicitly that no cyclic binding will be attempted during the analysis, this cannot be granted in any way. It is indeed easy to fix their unification algorithm if one sticks to the standard assumption that the analyzed language does not omit the occur-check in the unification procedure. Unfortunately, it is wellknown that many implemented CLP(H_{X}) languages (in particular, almost all Prolog systems) do omit the occur-check. We have discussed, apparently for the first time, the impact of this problem on data-flow analysis and how some commonly-used abstract domains can be modified in order to ensure precision and correctness. We have also shown how to modify our generic domain for structural information in order to deal with the majority of languages that employ unification without the occur-check.

For future work, the more urgent thing to do is to reconsider all the domains that have been proposed for the analysis of (constraint) logic programs and prove that they (or suitable modification of them) are correct with respect to rational trees and rational unification. This might be a non-trivial task, if one considers that there seems to be no published proof of the correctness of the Sharing

156  Chapter 4. Structural Information Analysis

domain [JL89], not even in the case of finite trees [Zaf95]^{6}.

6Enea Zaffanella in a desperate search for the proof, got in touch, in the fall of 1995, with three of the major authors on the subject of aliasing analysis. The result was disheartening: nobody knew where the proof could be found.

Range and Relations Analysis

\underline{Contents}

5.1 Introduction . . . . . . . . . . . . . . . . . 158 5.2 What Redundant Constraints Are For . . 163

5.2.1 Domain Reduction . . . . . . . . . . . . 165 5.2.2 Extracting Determinacy . . . . . . . . . 167 5.2.3 Static Call Graph Simplification …. 170 5.2.4 Future-Redundant Constraints . . . . . 174 5.2.5 Improving any Other Analysis . . . . . 175

5.3 Numbers as Leaves of Terms . . . . . . . 177 5.4 A Sequence of Approximations . . . . . . 178 5.5 Approximations for Sets of Reals . . . . . 184

5.5.1 Intervals . . . . . . . . . . . . . . . . . . 187 5.6 Approximations for Cardinalities . . . . . 191

5.6.1 An Example . . . . . . . . . . . . . . . 191 5.7 Approximations for Numerical Relations 194

158  Chapter 5. Range and Relations Analysis

5.7.1 Ordering Relationships . . . . . . . . . . 195 5.7.2 Bounded Differences . . . . . . . . . . . 197 5.7.3 Bounded Quotients . . . . . . . . . . . . 199

5.8 Approximations are Constraints . . . . . 200 5.9 Implicit Agents . . . . . . . . . . . . . . . . 211

5.9.1 Transitive Closure . . . . . . . . . . . . 211 5.9.2 Quantity Refinement . . . . . . . . . . . 212 5.9.3 Numeric Constraint Propagation …. 214

5.10 Numeric Agents . . . . . . . . . . . . . . . 214 5.10.1 Cardinality Agents . . . . . . . . . . . . 216 5.10.2 Constraint Agents . . . . . . . . . . . . 217 5.10.3 Quantity Arithmetic Agents . . . . . . . 219 5.10.4 Linear Refinement Agents . . . . . . . . 220 5.10.5 Relational Arithmetic Agents . . . . . . 222

5.11 Binding Agents . . . . . . . . . . . . . . . . 223 5.12 Making It Practical . . . . . . . . . . . . . 227

5.12.1 Representing Basic Constraints and Im-

plicit Agents . . . . . . . . . . . . . . . 228 5.12.2 Widenings . . . . . . . . . . . . . . . . . 233

5.13 Conclusion . . . . . . . . . . . . . . . . . . 234

5.1 Introduction

There are several CLP and cc languages that incorporate some kind of numeric domain. Here is a (certainly incomplete) list including the particular numeric domain(s) employed:

CHIP: numeric constraints over finite domains, linear rational constraints [DVS^{+}88]; CLP(R): real linear arithmetic (so to speak^{1}), delay mechanism for

non-linear constraints [JM87, JMSY92b];

QUAD-CLP(R): extends CLP(R) with the handling of quadratic constraints, rewriting them so that they can actually be decided upon or generating a conservative approximation for them (while still delaying them) [PB94];

CIAL: linear arithmetic on real intervals; it employs a linear constraint solver (based on preconditioned interval Gauss-Seidel method) in addition to the interval narrowing solver [CL94, LL94];

clp(Q, R): real^{2} and rational linear arithmetic, linear disequations included, delay mechanism for non-linear constraints [Hol95];

Prolog-III: linear rational arithmetic [Col90];

Prolog-IV: an ISO-compliant replacement for Prolog-III;

Trilogy: linear arithmetic both over the integers and over the reals [Vod88a, Vod88b];

CAL: (Contrainte Avec Logique) non-linear constraints over complex numbers [ASS^{+}88, SA89]. A parallel version exists and is called GDCC [AH92];

RISC-CLP(Real): non-linear constraints over (algebraic) real numbers [Hon92, Hon93, Cap93]; CLP(BNR): non-linear arithmetic on real intervals plus equations

1The CLP(R) system implements a domain based on floating-point numbers where rounding errors are simply disregarded. While the relevant literature unanimously regards CLP(R) as a CLP language, we follow this usage with embarrassment. Indeed, the ‘L’ in CLP(R) has no meaning whatsoever.

2The same comments as for CLP(R) apply.

over the integers [OV93];

CLP(F): domains include integers, reals, real-valued functions of one variable, and vectors of domain elements; function variables are constrained by functional equations and by putting interval constraints on the values of their derivatives at points and intervals [Hic94];

Newton: non-linear interval arithmetic with equations and inequalities, provides both constrained and unconstrained optimization primitives [BMV94];

clp(FD): finite domains à la CHIP [DC93];

Echidna: finite domains and real intervals arithmetic [HSS^{+}92];

cc(FD): finite domains [VSD92a, VSD92b];

AKL(FD): finite domains [CJH94, Jan94].

Roughly speaking, the target of the data-flow analysis we present is the derivation of numeric constraints that, at some program point p, are redundant. This means that they are guaranteed to hold whenever control reaches p. Consider, for instance, the selfcontained program and query in Figure 5.1. On exit from clause 2 the following constraints (and infinitely many others) are redundant:

N ∈ ℕ,  N ≥ 1,  (5.1)
S ∈ ℕ,  S ≥ 1,  (5.2)
N ≤ S,  (5.3)
S ≤ N^{2},  S = N^{2} + N.  (5.4)

Redundant constraints can be further classified as follows:

        sumto(0, 0).      % clause 1
        sumto(N, S) :-    % clause 2
            integer(N),
            N >= 1,
            N = N1 + 1,
            S = N + S1,
            sumto(N1, S1).
        ?- integer(N), sumto(N, S).

Figure 5.1: Sum of the first N naturals in CLP(N).

• truly redundant constraints: they are in the program’s text, but they are either implied by the constraints accumulated before reaching p (in this case they could be ignored) or they will be implied by the other constraints collected through any successful computation from p (in which case they can be subject to simplified treatment). We call these constraints past redundant and future redundant, respectively^{3}. On entry to clause 2 the constraint integer(N) is past redundant: any invocation of clause 2 happens in the context of a constraint store that entails it. The constraints integer(N) and N >= 1 are future redundant: any successful computation starting from clause 2 ends up with a constraint store entailing them. These facts can be established by means of a simple inductive argument.

• implicit constraints: they are not present in the program’s text, but they are guaranteed to hold if the computation arrives at p: thus the constraints (5.2)–(5.4) are implicit on exit from clause 2.

3Jørgensen et al. use a more restrictive notion of future redundancy [JMM91].

Notice that adding to a clause a constraint c that is implicit on exit from that clause would result in c being future redundant. As a last remark, the analysis which is the subject of this chapter is able to infer that the constraints (5.1)–(5.3) belong to the above categories.

Since in this work we restrict ourselves to considering only clause entry and clause (successful) exit as program points, the expressions numeric call-patterns and numeric success-patterns can also be used to denote redundant constraints at those points. However, the implicit/redundant terminology helps in understanding the applications.

Our interest in automated detection of implicit and redundant numeric constraints is motivated by the wide range of applications they have in semantics-based program manipulation. Moreover, while analysis techniques devoted to the discovery of implicit constraints over some Herbrand universe are well-known (e.g., depth-k abstractions [ST84, MS88], types [JB92, VCL94] and so on), in the field of numeric domains very little has been done. After our original proposal about employing constraint propagation techniques [BGL92, BGL93], Marriott and Stuckey envisaged the use of two domains: a simple one for sign analysis, Sign, and a (much) more complex domain, CHull, based on convex polyhedra. Following [BGL92, BGL93], Janssens et al. presented three interval-based approximations for the numerical leaves of (extended) Herbrand terms [JBE94]. The main merit of [JBE94] is that it contains the first published proposal on how to approximate the propagation of numerical information through unification constraints. Janssens et al. have also implemented their first approximations giving an indication that useful approximations of numerical values are feasible. These indications were reinforced, using a more complex numeric domain, in [Bag94]. Very recently, what probably is the first implementation of CHull, the Stan system, has been briefly described [Han96]. Stan, however, is limited to purely-numerical CLP languages.

We present a general methodology for the detection of redundant numeric constraints. The techniques we use for reasoning about arithmetic constraints come from the world of Artificial Intelligence, and are known under the generic name of constraint propagation [Dav87]. Notice that we do not commit ourselves to any specific CLP language, even though all the languages mentioned above can be profitably analyzed with the techniques we propose. In particular, we allow and reason about linear and non-linear constraints, integer, rational, and real numbers as well as domains based on intervals.

5.2 What Redundant Constraints Are For

In this section we show a number of applications for redundant constraints. Some of them are domain-independent, but we concentrate on redundant constraints over numeric domains. We will see that the range of situations where they prove to be useful is quite wide. It should then be clear that their automatic detection is very important for the whole field of semantics-based manipulation of CLP programs. The first four subsections are devoted to applications related to the compilation of CLP programs. Traditionally, this is one of the major interest areas for data-flow analysis. The remaining two subsections describe applications of redundant constraints to the improvement of other data-flow analyses.

It must be observed that here we are dealing with some kind of information that might be useful in the compilation process by allowing for the production of faster code. In this work the focus is on techniques for automatic deduction of run-time program properties. Deciding in which cases and to what extent this information gives rise to actual speedups is a completely different and complicated issue. While some compile-time transformation (e.g., domain reduction, determinacy exploitation, and call-graph simplification) can be definitely recognized as optimizations, others (like constraint anticipation or refinement [MS93]) require careful consideration as their usefulness depends on several factors. Deep knowledge of the constraint solver and extensive experimentation might be necessary for this purpose. One must also be aware of the interplay between different optimizations, as the combination of two transformations is not guaranteed to be an optimization, even though the original ones were such, when applied in isolation. Another point to be taken into account is that a transformation applied in one point of the program might prevent the application of another transformation at another program point. When one of these situations occur a tradeoff is faced and some kind of compromise must be found with the aim of maximizing benefits. Sometimes knowledge about the particular system at hand can be enough to definitely conclude that a particular (combination of) optimization(s) is worthwhile. In other cases the decisions about what transformations to apply, and where, are more complex and must be dealt with more flexibly by the compiler and/or the user. Techniques devoted to the solution of these problems include:

• performing complexity/cost analysis and using the results as a base for compilation decisions;

• two-phase compilation, e.g., compiling first an instrumented version of the program, profiling it with some “representative” input, and using the profile data to drive the final compilation step;

• simple heuristics, e.g., by analyzing the program’s call graph, and deserving more work/optimization to code which is more deeply nested;

• user intervention by means of program annotations; in this task the user can be assisted by providing him with information originating from the above techniques.

5.2.1 Domain Reduction

In CLP systems supporting finite domains, like CHIP, clp(FD), and Echidna, variables can range over finite sets of integer numbers. These sets must be specified by the programmer. There are combinatorial problems, such as n-queens, where this operation is trivial: variables denoting row or column indexes range over {1, …, n}. For other problems, like scheduling, the ranges of variables are not so obvious. Leaving the user alone in the (tedious) task of specifying the lower and upper bounds for any variable involved in the problem is inadvisable. On one hand the user can give bounds that are too tight, thus loosing solutions. On the other hand he can exceed in being conservative by specifying bounds that are too loose. In that case he will incur inefficiency, as finite domains constraint solvers work by gradually eliminating values from variable’s ranges.

A solution to this problem is either to assist the user during program development or to provide him with a compiler able to tighten the bounds he has specified. In this case the programmer can take the relaxing habit of being conservative, relying on the compiler’s ability of achieving domain reduction. Whatever the programmer’s habit is, domain reduction at compile-time can be an important optimization as possibly many inconsistent values can be removed once and for all from the variable’s domains. This has to be contrasted with the situation where these inconsistent values are removed over and over again during the computation.

The following example is somewhat unnatural, but it shows how it can be difficult for an unassisted human to provide good variable’s bounds. In contrast, it shows how relatively tight bounds can be “hidden” in a program and how they can be “discovered” by means

             mc(N, N-10) :-    % clause 1
                 N > 100.
             mc(N, M) :-       % clause 2
                 N <= 100,
                 mc(N+11, U),
                 mc(U, M).

Figure 5.2: McCarthy’s 91-function in CLP(N).

of data-flow analysis. The program in Figure 5.2 is a CLP(N) version of the McCarthy’s 91-function. If the program is complemented with the domain declaration

    domain mc([0 .. 200], [0 .. 200]).

the China analyzer is able to derive the following success-patterns

for mc(A, B):

101 ≤ A ≤ 200,  91 ≤ B ≤ 190,  for clause 1;
0 ≤ A ≤ 100,  91 = B,  for clause 2.

Notice how the analyzer correctly infers that any successful derivation from the second clause must end up with an answer constraint entailing A = 91. The same pattern is derived with the more liberal declaration^{4}

    domain mc([0 .. 200], _).

With no domain declaration at all the inferred patterns are

A ≥ 101,  B ≥ 91,  for clause 1;
A ≤ 100,  B = 91,  for clause 2.

4The underscore sign ‘_’ stands, in this context, for the set of all the integer numbers.

5.2.2 Extracting Determinacy

In the history of efficient Prolog execution a major role has been played by the avoidance of unnecessary backtracking, since this is the principal source of inefficiency. These efforts go back to the WAM [War83] with the indexing mechanism used to reduce shallow backtracking. A more general way of avoiding backtracking is to use global analysis for detecting conditions under which clauses may succeed in a program (determinacy analysis). Run-time tests to check this conditions may allow for the elimination of choice points or, at least, for the reduction of backtracking search (determinacy exploitation).

Notice that backtracking in CLP can be significantly more complex than in Prolog. The reason is that in CLP languages it is not enough to store a reference to variables that have become bound since the last choice point creation, and to unbind them on backtracking. In CLP it is generally necessary to record changes to constraints, as expressions appearing in them can assume different forms while the computation proceeds [JM94].

We show here, more or less following the exposition in [DRRS93], how redundant constraints can be used for determinacy discovery and exploitation. Consider a CLP program P and a clause R in P of the form

R : p(X̄) :− c □ q_{1}(X̄_{1}), …, q_{n}(X̄_{n}).  (5.5)

Suppose now that data-flow analysis of P computes the successpattern ϕ for clause R. Substituting the clause

R′ : p(X̄) :− ϕ ∧ c □ q_{1}(X̄_{1}), …, q_{n}(X̄_{n}).  (5.6)

for R yields a program that is logically equivalent to P. Suppose also the analysis derives that ψ is a correct call-pattern for the atom p(X̄). This means that, whenever p(X̄) is selected in a successful computation path, ψ holds.^{5} Dawson et al. define the clause condition of R as Φ = ψ ∧ ϕ. The clause condition Φ is a necessary condition for R to succeed when p(X̄) is invoked from a successful context. Every successful computation calling R is such that, on successful exit from R, the accumulated constraints entail Φ. In other words, if the concrete constraint store on entry to clause R is incompatible with Φ, then the current computation branch can safely be abandoned. This fact can be captured by rewriting clause R into

R′′ : p(X̄) :− Φ ∧ c □ q_{1}(X̄_{1}), …, q_{n}(X̄_{n}).  (5.7)

Let now P′′ be the program obtained by transforming each clause of the form (5.5) into the form (5.7) as explained. It turns out that P and P′′ are logically equivalent, and that the exposed clause conditions can be used for detecting and exploiting determinacy in the compilation of P.^{6}

Suppose the predicate p is defined in P by clauses R_{1}, …, R_{m} with respective success-patterns ϕ_{1}, …, ϕ_{m} and clause conditions Φ_{1}, …, Φ_{m}. What can happen is that, for each i, j = 1, …, n with i ≠ j, the constraint ϕ_{i} ∧ ϕ_{j} is unsatisfiable. In this case p is deterministic, namely, for each actual call-pattern that is strong enough we can select the unique clause that might bring the computation to success. When the above condition fails, we may still have that Φ_{i} ∧ Φ_{j} is unsatisfiable whenever i ≠ j. Thus p might not be deterministic in itself, but we are guaranteed that in P it is always used in a determinate way.

6Notice that correct call-patterns are, in general, stronger than “plain” callpatterns. This is because only successful computation paths are considered for correct call-patterns. As a consequence, the present discussion remains valid even if plain call-patterns are considered.

          fib(N, F) :-              % clause 1
              N = 0, F = 1.
          fib(N, F) :-              % clause 2
              N = 1, F = 1.
          fib(N, F) :-              % clause 3
              N > 1,
              F = F1 + F2,
              fib(N-1, F1),
              fib(N-2, F2).

Figure 5.3: Fibonacci’s sequence in CLP(N).

In both cases, when the conditions are simple enough to be checked, it is possible to avoid the creation of a choice point jumping directly to the unique clause that has a chance of success. Weaker assumptions still allow to exclude clauses from search by partitioning the set of clauses into “mutually incompatible” subsets. Of course, determinacy exploitation requires the existence of an adequate indexing mechanism [HM89, RRW90, CRR92]. As an example consider the famous CLP program, reproduced in Figure 5.3, expressing the Fibonacci sequence. China derives the following success-patterns for the fib program:

N = 0,  F = 1,  for clause 1;
N = 1,  F = 1,  for clause 2;
N ∈ {2, 3, … },  F ∈ {2, 3, … },  for clause 3.

(The same patterns would have been derived even if N > 1 did not appear in clause 3. China can also derive N <= F for clause 3.) Notice that, when fib is called with the a definite (or ground) first argument, a simple test allows to select the appropriate clause without creating a choice point. When fib is called with its second argument instantiated then a similar test allows at least to discriminate between clause 3, if F ≥ 2 (no choice point), and clauses 1 and 2, if F = 1 (a choice point is necessary). In both cases some calls can be made to hit an immediate failure instead of proceeding deeper before failing or looping forever, e.g., fib(1.5, X), fib(X, -1).

5.2.3 Static Call Graph Simplification

Suppose we are given a methodology for approximate deduction of implicit constraints. Then, if the approximate constraint system has a non-trivial notion of consistency^{7}, we also have a methodology for approximate consistency checking which we can use for control-flow analysis of CLP programs. By soundness, when false is derived as an implicit constraint we can safely conclude that the original set of constraints was unsatisfiable, and that the computation branch responsible for this state of affairs cannot possibly lead to any success.

This information can be employed at compile-time to generate a simplified call graph for the program at hand. Let

R : p(X̄) :− c □ q_{1}(Ȳ_{1}), …, q_{n}(Ȳ_{n}).

be a program clause, and let the predicate q_{i} be defined by clauses R_{i_{1}}, …, R_{i_{m_{i}}}, for i = 1, …, n. While performing the analysis we may discover that whenever we use clause R_{i_{j}}, with 1 ≤ j ≤ m_{i}, to resolve with q_{i}(Ȳ_{i}), we end up with an unsatisfiable constraint.

7This is not the case for, say, groundness analysis, where abstract constraints are always satisfiable. A non-trivial approximation of inconsistency can be obtained with the domains that will presented later in this chapter, with the domain for simple types of Section 3.3.2 on page 67, with structural information analysis as in Chapter 4, with depth-k abstractions, types and so forth.

In this case we can drop the edge from the q_{i}(Ȳ_{i}) call in the above clause to R_{i_{j}} from the syntactic call graph of the program. This simplification can be used for generating faster code.^{8} We illustrate this point by means of an example. China, when presented with the fib program of Figure 5.3, produces the following call graph representation:

3 :− ⟨{2, 3}, {1, 2, 3}⟩.  (5.8)

The above notation can be read as follows:

if a call to clause 3 has to succeed, then only clauses 2 and 3 can successfully used to resolve with the first recursive call, while the second recursive call is unrestricted, i.e., all the clauses for fib can lead to success.

In other words, when treating the first recursive call of clause 3, clause 1 can be forgotten. This information allows for search space reduction without any overhead, in the case where the third clause of fib is called with the first argument uninstantiated, that is, when search is not avoidable. Figure 5.4 shows how this can be achieved by means of a simple compilation scheme in the setting of the WAM and its extensions [JMSY92a].

Notice how this simple transformation reduces the amount of backtracking. In fact, every time clause 3 is invoked a pointless call to clause 1 is avoided, with the consequent saving of one backtracking. It is easy to think about more involved examples where the pruned computation branch would have proceeded deeper before failing, thus wasting more work. When the number of applicable clauses is found to be one, a choice point can be avoided, thus achieving greater savings. In these cases determinacy is exploited without any run-time effort. The optimization is always

8We do not want to make a strong claim, but we have not found any published proposal for this optimization besides [Bag94].

fib/3_1:    try_me_else fib/3_2

< code for clause 1 >

fib/3_2:    retry_me_else fib/3_3

fib/3_2a:   < code for clause 2 >

fib/3_3:    trust_me
fib/3_3a:   ...

           call fib/3_2_3
           call fib/3_1
           ...

fib/3_2_3:  try fib/3_2a

           trust fib/3_3a

Figure 5.4: Fragment of WAM-like code for the 3rd clause of fib to be executed when the 1st argument is not definite.

achieved without any time overhead at the price of at most a small, constant increase of space usage for additional code. An example where choice point creations are avoided is the following:

square(0, 0).              % clause 1
square(X+1, Y+2*X+1) :-    % clause 2
square(X, Y).
pythagoras(X, Y, Z) :-     % clause 3
X < Y,
Y < Z,
SX + SY = SZ,
square(X, SX),
square(Y, SY),
square(Z, SZ).

The detected call graph in this case is

2 :− {1, 2},
3 :− {1, 2}, {2}, {2},

showing that the second and third procedure calls of clause 3 can be compiled as direct jumps to the code for clause 2.

The strong points of call graph simplification are the following:

• only a small space overhead is incurred by the optimized program;

• no time overhead: the optimized program will never be slower than the unoptimized one;

• almost no additional cost for the analysis: China simply records which clauses have been successfully used to resolve with each atom in the clauses’ bodies.

As a final remark, notice that the simplified call graph for fib given in (5.8) is actually obtained by abstracting away the relational information from

3 :− {⟨2, 1⟩, ⟨3, 2⟩, ⟨3, 3⟩}.  (5.9)

In addition to the fact that clause 1 can be disregarded when dealing with the first recursive call in clause 3, (5.9) specifies that, when clause 2 is selected to resolve with the first recursive call, then only clause 1 is a sensible choice for the second recursive call. Similarly, when clause 3 is recursively called first, the subsequent call should ignore clause 1. In summary, while the static call graph of fib contains 9 edges, the simplified call graph represented by (5.9) specifies that only 3 of them can lead to a successful computation. This additional information, which, again, is cheaply obtained as a byproduct of the analysis, can also be useful. Its direct exploitation

     mortgage(P, T, I, B, MP) :-        % clause 1
         T = 1,
         B = P*(I + 1) - MP.
     mortgage(P, T, I, B, MP) :-        % clause 2
         T > 1,
         P >= 0,
         mortgage(P*(I + 1) - MP, T - 1, I, B, MP).

Figure 5.5: Standard mortgage relationship in CLP(R).

at run-time requires a mechanism whereby the choice of one clause to resolve with a body atom influences the choices of the clauses for subsequent atoms. Another application concerns guiding program transformations such as unfolding: a partial evaluator would immediately know that clause 3 can be unfolded in at most three different, sensible ways.^{9} Finally, the simplified call graph can be used in order to simplify subsequent analyses of the same program.

5.2.4 Future-Redundant Constraints

As mentioned in the introduction, we say that a constraint is future redundant if, after the satisfiability check, adding or not adding it to the current constraint (i.e., the constraint accumulated so far in the computation), will not affect any answer constraints. Consider the mortgage program in Figure 5.5. In any derivation from the second clause the constraint T′ = T−1∧T′ > 1 or T′ = T−1∧T′ = 1 will be encountered, and both imply T > 1. Thus T > 1 in the second clause is future redundant. If T is uninstantiated, not adding the future redundant constraint reduces the “size” of the current constraint, thus reducing the complexity of any subsequent satisfiability check. A dramatic speed-up is obtainable thanks to this optimization [JMM91]. Notice that the definition of future redundant constraint given in [JMM91] is more restrictive than ours and, while allowing a stronger result for the equivalence of the optimized program with respect to the original one, it fails to capture situations which can be important not only for compilation purposes (see Section 5.2.5 below). As an example, consider a version of the fib program in Figure 5.3 on page 169, where the constraint F >= 2 has been added in the recursive clause. This is future redundant for our definition (and is recognized as such by the analyzer), while it is not for the definition in [JMM91].

9A small experiment with the CLP(R) system: the queries fib(19, F) and fib(N, 6765) took 1.16 and 3.65 seconds, respectively. By unfolding clause 3 a tenfold speedup is obtained: 0.1 and 0.38 seconds, respectively.

5.2.5 Improving any Other Analysis

Detecting redundant constraints improves the precision (and sometimes also the efficiency) of any other analysis. This is due to the ability, described in Section 5.2.3, of discovering computation branches which are dead. The obvious implication is that these branches can be safely excluded from analysis: the result is better precision, because the merge-over-all-paths operation needed for ensuring soundness [CC77] has potentially a less dramatic effect, and possibly improved efficiency, because less branches need to be analyzed.

We illustrate the first point by means of an example. Consider the following predicate definition:

                    r(X, Y, Z) :-
                        Y < X,
                        Z = 0.
                    r(X, Y, Z) :-
                        Y >= X,
                        Z = Y - X.

This defines the so called ramp function, and is one of the linear piecewise functions which are used to build simple mathematical models of valuing options and other financial instruments such as stocks and bonds [LMY87]. Suppose we are interested in groundness analysis of a program containing the above clauses. A standard groundness analyzer cannot derive any useful success-pattern for a call to r(X, Y, Z) where nothing is known about the groundness of variables, even though the “real” call-pattern implied Y < X. In contrast, in the same situation a definiteness analyzer employing also a numerical domain (or supplemented with the simplified call graph described in Section 5.2.3) can deduce the success-pattern ground(Z). This is due to the ability of recognizing that, in the mentioned context, only the first clause is applicable. Indeed, this is what happens in the analysis of the option program distributed with the CLP(R) system.

Improving the Results of Some Other Analyses

The previous section showed how our analysis can generally improve the others. There are, however, more specific situations where its results may be of help. For example, the freeness analysis proposed in [DJBC93] can be greatly improved by the detection of future redundant constraints. In fact, their abstraction is such that constraints like N ≥ 0 “destroy” (often unnecessarily) the freeness of N. This kind of constraints are very commonly used as clause guards, and many of them can be recognized as being future redundant. This information imply that they do not need to be abstracted, with the corresponding precision gain.

The analysis described in [Han93] aims at the compile-time detection of those non-linear constraints, which are delayed in the CLP(R) implementation, that will become linear at run time. This analysis is important for remedying the limitation of CLP(R) to linear constraints by incorporating powerful (and computationally complex) methods from computer algebra as the ones employed in RISC-CLP(Real). With the results of the above analysis this extension can be done in a smooth way: non-linear constraints which are guaranteed to become linear will be simply delayed, while only the other non-linear constraints will be treated with the special solving techniques. Thus, programs not requiring the extra power of these techniques will be hopefully recognized as such, and will not pay any penalties. The analysis of [Han93] is a kind of definiteness. One of its difficulties shows up when considering the simplest nonlinear constraint: X = Y ∗ Z. Clearly X is definite if Y and Z are such. But we cannot conclude that the definiteness of Y follows from the one of X and Z, as we need also the condition Z ≠ 0. Similarly, we would like to conclude that X is definite if Y or Z have a zero value. It should then be clear how the results of the analysis we propose can be of help: by providing approximations of the concrete values of variables, something which is not captured by common definiteness analyses while being crucial when dealing with non-linear constraints.

5.3 Numbers as Leaves of Terms

For the purpose of bounds and relation analysis of CLP(H_{N}) languages we need ways to approximate both numbers and multisets of numbers. The need for numbers is obvious: we want to represent and compute approximations of the values which numeric variables can take. While this would be enough for simple CLP(N) and CLP(H, N) languages, this is not the case for CLP(H_{N}) languages, where numerical values can occur as leaves of extended Herbrand terms.

An important observation is that the numerical properties we are about to introduce are monotonic. If, at some program point, the numerical leaves of terms that can be bound to a variable X are constrained to be, say, greater than 1, they will continue to do so as the computation progresses. An important consequence is that we can formalize concrete constraint stores as possibly infinite sets of tuples of ground terms. For example, the constraint store containing

A = c(X, Y, Z),
B = k(s(Cx, Cy, Cz, R)),
R ∗ R ≥ (X − Cx) ∗ (X − Cx) + (Y − Cy) ∗ (Y − Cy)
+ (Z − Cz) ∗ (Z − Cz),

and projected onto the pair of variables (A, B) is represented by the following infinite set of pairs of ground terms:

\{ (c(x, y, z), k(s(c_{x}, c_{y}, c_{z}, r))) | \begin{matrix}x, y, z, c_{x}, c_{y}, c_{z}, r ∈ ℝ \\ r^{2} ≥ (x − c_{x})^{2} + (y − c_{y})^{2} \\ +(z − c_{z})^{2} \end{matrix} \}.

The overall analysis domain that is the subject of this chapter is of the kind Pattern(something), where Pattern(·) is the generic structural domain of Chapter 4. Describing that ‘something’ is the purpose of the following sections.

5.4 A Sequence of Approximations

We will now start describing how we abstract a set of tuples of terms for the purpose of range and relations analysis. This is done by setting up chain of subsequent approximations starting from the very concrete domain constituted by the complete lattice

D^{♭♭} =^{def} ℘(T^{m})  (5.10)

ordered by set inclusion.

First of all, we abstract each ground term with the multiset of its numerical leaves.^{10}

Definition 74 (Numerical leaves.) The multiset of numerical leaves of a term is given by the function nl^{+} : T → ℘f^{+}(ℝ) defined as follows, for each t ∈ T :

nl^{+}(t) =^{def} \{ \begin{matrix}⟅t⟆, & if t is a number; \\ ⨄_{i=1}^{a} nl^{+}(t_{i}), & if t = f(t_{1}, …, t_{a}); \\ ∅, & otherwise. \end{matrix}

This brings us consider the intermediate domain

D^{♭} =^{def} ℘(℘f^{+}(ℝ)^{m}),  (5.11)

still ordered by set inclusion, which is connected to D^{♭♭} by means of the abstraction function

λT̂ ∈ ℘(T^{m}) . { (nl^{+}(t_{1}), …, nl^{+}(t_{m})) | (t_{1}, …, t_{m}) ∈ T̂ }. (5.12)

The function (5.12) is a complete join-morphism by its very definition. Consequently, we have just defined a Galois connection between D^{♭♭} and D^{♭}.

We now face the problem of representing sets of m-tuples of finite multisets of real numbers. The first approximation which comes to mind consists in flattening the multisets and getting rid of the outermost powerset construction (an approximation often employed in abstract interpretation). This would lead to consider the domain

10The reader who has skipped the chapter on mathematical background might want to go back to Section 2.3 on page 26 where multiset notation is explained.

D_{q}^{♮} =^{def} ℘(ℝ)^{m},  (5.13)

which is an abstraction of D^{♭} through the abstraction function α_{q} : D^{♭} → D_{q}^{♮} given by

λM̂ ∈ D^{♭} . ( ⋃_{M̄∈M̂} ζ(π_{1}(M̄)), …, ⋃_{M̄∈M̂} ζ(π_{m}(M̄))).  (5.14)

Of course, if we aim at a practical data-flow analysis, we would need to approximate ℘(ℝ)^{m} further: by using m-tuples of intervals, for instance. However, even the abstraction (5.14) alone implies an excessive loss of precision. For example, the set

Ê =^{def} {(⟅1⟆, ⟅2⟆, ⟅3⟆), (⟅4⟆, ⟅5⟆, ⟅6⟆)}  (5.15)

is abstracted by (5.14) into

α_{q}(Ê) = ({1, 4}, {2, 5}, {3, 6}),  (5.16)

which is also the abstraction of, say,

{(∅, ⟅5⟆, ⟅3, 6⟆), (⟅1, 4⟆, ⟅2⟆, ∅)}.

We have thus lost information on the cardinalities of the multisets which form the tuples of Ê, as well as relational information on these multisets. While it is true that each m-tuple Ē ∈ Ê satisfies

∀x π_{1}(Ē) : ∀y π_{2}(Ē) : ∀z π_{3}(Ē) : x < y < z,

this cannot be concluded by looking at α_{q}(Ê) only.

The above discussion is fairly obvious, of course, since information loss is the very essence of proper abstraction. The point is that we do not want to lose too much precision neither on the cardinalities of the multisets nor on the relationships which exist between the elements of each multiset. The reasons why these kinds of information are important for our application will become more clear later. However, they can be appreciated immediately by means of a couple of observations. First of all, qualitative constraints of the kind X < Y arise frequently in constraint programming. They can be combined with quantitative constraints like, say, X ≥ 2 in order to obtain new qualitative information: Y > 2 in the present example. So, relational information is important.

Then, suppose we have three sets of terms: T_{1}, T_{2}, and T_{3}. Suppose also we know that all the numerical leaves that occur in T_{1} are numerically less than those of T_{2}, which, in turn, are less than those of T_{3}. Can we conclude that all the numerical leaves of T_{1} are less than those of T_{3}? The answer is

“yes, provided that T_{2} has at least one numerical leaf ”.

There are other possible inferences which are based on some nonemptiness condition. However, in order to capture the property of non-emptiness for multisets, non-emptiness alone is not strong enough. Suppose you have an unknown term

t =^{def} f(t_{1}, t_{2}),

where t_{1} and t_{2} are two further unknown terms. If you only know that t has a non-empty multiset of numerical leaves then all you can say is that t_{1} or t_{2} have at least one numerical leave. Stated differently, if you can reason in terms of non-emptiness only, nothing can be concluded: t_{1} might not have any numerical leave, and the same can happen for t_{2}, though not at the same time. If cardinalities of the multisets can be expressed a more refined reasoning is possible, since the number of numerical leaves of t, u_{1}, and u_{2} are tied by the formula

∥nl^{+}(t)∥ = ∥nl^{+}(t_{1})∥ + ∥nl^{+}(t_{2})∥.

Similar situations occur frequently in abstract interpretation. For instance, if the groundness of variables has to inferred precisely one must use a strictly more powerful domain capturing groundness dependencies.

Stated that cardinalities and binary relations are important to us, we supplement D_{q}^{♮} with two further domains,

D_{c}^{♮} =^{def} ℘(ℕ)^{m}  (5.17)

and

D_{r}^{♮} =^{def} ℘(ℝ^{2})^{m(m−1)/2}.  (5.18)

The abstraction α_{c} : D^{♭} → D_{c}^{♮} keeps track of cardinalities. It is given by

λM̂ ∈ D^{♭} . ({ ∥π_{1}(M̄)∥ | M̄ ∈ M̂ }, …, { ∥π_{m}(M̄)∥ | M̄ ∈ M̂ }). (5.19)

The additional domain D_{r}^{♮}, instead, records the Cartesian product (a binary relation) between the values occurring in the i-th and the j-th multiset, for i < j. This restriction to the set of indexes

I_{m} =^{def} { (i, j) | i, j = 1, …, m, i < j }  (5.20)

is in order to avoid redundancies. Clearly, S_{i} × S_{i} does not convey more information that S_{i} itself, and for any two sets S_{i} and S_{j} we have S_{j} × S_{i} = (S_{i} × S_{j})^{−1}. Observe that excluding useless

j̃(k) =^{def} ⌊\frac{\sqrt{8k − 7} + 1}{2}⌋ + 1,  for k = 1, …, \frac{m(m − 1)}{2};
ĩ(k) =^{def} k − \frac{(j̃(k) − 1)(j̃(k) − 2)}{2}, for k = 1, …, \frac{m(m − 1)}{2};
(ĩ, j̃)^{−1}(i, j) =^{def} i + \frac{(j − 1)(j − 2)}{2},  for (i, j) ∈ I_{m}.
Figure 5.6: A bijection (ĩ, j̃): I_{m} → {1, …, m(m − 1)/2}.

redundancies allows us, among other things, to avoid the trouble of specifying consistency conditions among the redundant pieces. Once one has specified a bijection

(ĩ, j̃): I_{m} → {1, …, m(m − 1)/2},  (5.21)

such as the one given in Figure 5.6, the abstraction α_{r} : D^{♭} → D_{r}^{♮} can be expressed as

λM̂ ∈ D^{♭} . (R(1, M̂), …, R(m(m − 1)/2, M̂)),  (5.22)

where, for each k = 1, …, m(m − 1)/2,

R(k, M̂) =^{def} ⋃_{M̄∈M̂} ζ(π_{ĩ(k)}(M̄)) × ζ(π_{j̃(k)}(M̄)),  (5.23)

Now, our first approximation of D^{♭} is given by the product of D_{c}^{♮}, D_{q}^{♮}, and D_{r}^{♮}, namely

D^{♮} =^{def} D_{c}^{♮} × D_{q}^{♮} × D_{r}^{♮}.  (5.24)

This is clearly more precise than D_{q}^{♮} alone. The domain D^{♮} includes some redundancy, since many of its elements represent the same element of D^{♭}. This situation could be rectified by considering, instead of the straight product, the reduced product of D_{c}^{♮}, D_{q}^{♮}, and D_{r}^{♮} [CC79, CC92a]. We will not do that, however, since we proceed by performing a further approximation step on D^{♮}.

Actually, we consider a family of such approximations. Each member of the family is obtained by providing separate approximations for D_{c}^{♮}, D_{q}^{♮}, and D_{r}^{♮}. The considered family of abstract domains is thus given by any

D^{♯}(A_{ℝ}, A_{ℕ}, A_{ℝ^{2}}) =^{def} A_{ℕ}^{m} × A_{ℝ}^{m} × A_{ℝ^{2}}^{m(m−1)/2},  (5.25)

where A_{ℝ}, A_{ℕ}, and A_{ℝ^{2}} are approximations of ℘(ℕ), ℘(ℝ), and ℘(ℝ^{2}), respectively. We will show later how we can approximate the reduction of the product (5.25), thus obtaining an approximation of the reduced product of D_{c}^{♮}, D_{q}^{♮}, and D_{r}^{♮}.

The next three sections are devoted to the introduction of the classes of approximations we consider for the representation of sets of cardinalities, sets of real numbers, and binary relations between such sets. We will give the properties they must satisfy for our purposes together with some representative examples.

5.5 Approximations for Sets of Reals

This first kind of approximation conveys quantitative information on the values that numerical leaves can take. Here is a definition that, adopting the closure operator approach to abstract interpretation, spells out precisely what we require for this purpose.

Definition 75 (ℝ-approximation.) A ℝ-approximation is any algebra of the form^{11}

Ȧ_{ℝ} =^{def} ⟨A_{ℝ}, ⊞, ⊟_{1}, ⊟_{2}, ⧆, ⧄, sin , … ⟩,

11The subscripts of ⊟_{1} and ⊟_{2} denote the arity: they are the approximations of unary and binary minus, respectively.

where

1. A_{ℝ} ⊆ ℘(ℝ) is an abstraction of ℘(ℝ), namely, there exists an operator^{12} A_{ℝ} ∈ uco(℘(ℝ), ⊆) such that A_{ℝ} = A_{ℝ}(℘(ℝ)). Moreover, ∅ ∈ A_{ℝ}.

2. ⊞, ⊟_{1}, ⊟_{2}, ⧆, ⧄, sin , … are functions in A_{ℝ}^{k} → A_{ℝ}, for some m ∈ ℕ, approximating the linear extensions of their obvious (non-boxed) counterparts in ℝ^{k} → ℝ. For instance, ⊞: A_{ℝ}^{2} → A_{ℝ} is such that, for each A_{1}, A_{2} ∈ A_{ℝ},

{ x_{1} + x_{2} | x_{1} ∈ A_{1}, x_{2} ∈ A_{2} } ⊆ A_{1} ⊞ A_{2}.

We will use the symbols ⊗ and ⊕ to denote the glb (set intersection) and the lub operators, respectively, of the complete lattice (A_{ℝ}, ⊆).

The reader who wonders why we have not defined, for instance,

A_{1} ⊞ A_{2} =^{def} A_{ℝ}({ x_{1} + x_{2} | x_{1} ∈ A_{1}, x_{2} ∈ A_{2} }),

is referred to the next section, where we will briefly review a wellknown ℝ-approximation.

Sometimes one needs approximations for subsets of the extended reals ℝ_{∞} =^{def} ℝ∪{−∞, +∞}, instead of just the reals. This can happen because the language to be analyzed employs IEEE-754 and/or IEEE-854 extended arithmetic^{13}. Some extra complications arise (especially in the implementation) when dealing with the extended reals.

12Notice how we overload A_{ℝ} to denote both the approximation’s domain and the upper closure operator that maps each subset of the reals into the least element of the domain that contains it. Thus, A_{ℝ}: ℝ → A_{ℝ} is a sort of constructor for A_{ℝ} ⊆ ℘(ℝ). We will adopt the same convention for all the approximations that follow.

13SICStus Prolog implements such a language [SIC95].

Another reason for dealing with the extended reals is when one of the results which are sought from the analysis is possible overflow information. In this case the values +∞ and −∞ indicate a potential arithmetic overflow in the computation.

Actually, we are about to introduce ℝ_{∞}-approximations because we need them in order to define an ℝ^{2}-approximation called bounded quotients (see Section 5.7.3 on page 199).

Definition 76 (ℝ_{∞}-approximation.) A ℝ_{∞}-approximation is an algebra of the form

Ȧ_{ℝ_{∞}} =^{def} ⟨A_{ℝ_{∞}}, ⊞, ⊟_{1}, ⊟_{2}, ⧆, ⧄, sin , … ⟩,

where

1. A_{ℝ_{∞}} ⊆ ℘(ℝ_{∞}), with ∅ ∈ A_{ℝ_{∞}}, is an abstraction of ℘(ℝ_{∞}) through the operator A_{ℝ_{∞}} ∈ uco(℘(ℝ_{∞}), ⊆).

2. ⊞, ⊟_{1}, ⊟_{2}, ⧆, ⧄, sin , … are functions in A_{ℝ_{∞}}^{k} → A_{ℝ_{∞}} approximating the linear extensions of their non-boxed counterparts in ℝ_{∞}^{k} → ℝ_{∞}.

3. Moreover, all the above functions satisfy the following property: for each op : A_{ℝ_{∞}}^{k} → A_{ℝ_{∞}} and each A_{1}, …, A_{k} ∈ A_{ℝ_{∞}}, if it happens that +∞ ∈ op (A_{1}, …, A_{k}) then there must exist x_{1} ∈ A_{1}, …, x_{k} ∈ A_{k} such that op(x_{1}, …, x_{k}) = +∞. Likewise for −∞.

The same notational conventions introduced for ℝ-approximations will be used.

Note that condition 3 above imposes a limit on the crudeness of the approximate operators. In words, the extra-symbols +∞ and −∞ cannot be thrown in the results without reasons, but only when the linear extension of the approximated operation would have produced them. As we will see, this requirement imposes some extra care in the actual definition and implementation of a ℝ_{∞}-approximation.

5.5.1 Intervals

One commonly used ℝ-approximation is constituted by intervals. They have several advantages [AH83, Cle87, Moo66, Dav87]:

1. they have a compact representation. Usually two numbers drawn from some subset of ℝ ∪ {−∞, +∞} are enough, plus possibly two booleans to indicate closedness or openness at each end.

2. Intervals are closed under intersection; and

3. the image of an interval under a continuous function is itself an interval.

Definition 77 (Open/closed intervals.) Let B ⊆ ℝ be any set of real numbers. The set B_{∞} of boundaries over B is given by

B_{∞} =^{def} B ∪ {−∞, +∞},

and is totally ordered by the natural extension of < over B_{∞}. Consider the set

I_{B} =^{def} { { x ∈ ℝ | l ▷◁_{l} x ▷◁_{u} u } | l, r ∈ B_{∞}, ▷◁_{l}, ▷◁_{r} ∈ {<, ≤} }.

The ⊗ operator over I_{B} is set intersection, while ⊕ is given by the convex hull, that is, for each I_{1}, I_{2} ∈ I_{B},

I_{1} ⊕ I_{2} =^{def} { x ∈ ℝ | ∃l, u ∈ I_{1} ∪ I_{2} . l ≤ x ≤ u }.

By endowing I_{B} with a family of arithmetic operators as required by Definition 75 we obtain a ℝ-approximation. We will denote it by İ_{B} and call it an interval approximation over B.

Suppose that {0, 1} ⊆ B and that −x ∈ B whenever x ∈ B. Then it is possible to define the ⊞ and ⧆ operations so that I_{B} is a commutative ringoid with identity. Namely [KM81], for each I, I_{1}, I_{2} ∈ I_{B}:

I_{1} ⊞ I_{2} = I_{2} ⊞ I_{1},  I ⊞ {0} = I,
I_{1} ⧆ I_{2} = I_{2} ⧆ I_{1},  I ⧆ {1} = I,
I ⧆ {0} = {0},

{−1} is the unique interval which is the additive inverse of {1}, the multiplicative inverse of itself, and distributes with respect to ⊞ and ⧆. In case B is unbounded, ⊞ and ⧆ can be made to be associative. Distributivity, however, does not hold, even in the extreme case where B is ℝ itself.

In practice, one chooses B to be a computable set of numbers, e.g., some family of rational or floating point numbers. Rational numbers would allow exact arithmetic computations to be performed, assuming the availability of arbitrarily large integers. Exact computations, however, have the disadvantage that the gcd operations, while taking time, cannot prevent the formation of huge numerators and denumerators in the boundaries’ representations. As usual, one can trade precision for efficiency by considering only interval boundaries with acceptably compact ratios [EF92].

Of course, one can consider open intervals only or closed intervals only. Some authors advocate this last possibility for the following reasons [Dav87]:

1. any bounded set of values admits a unique minimal closed interval which contains it;

2. numbers are a special case of closed interval;

3. the image of a closed bounded interval under a continuous function is a bounded closed interval.

These considerations, unfortunately, have little value in practice. The reason is that our boundary values must be machine representable. For instance, if we employ single-precision IEEE floating point numbers, we can represent the bounded set of values {2} as [2, 2], but the best representation we can get for \{\sqrt{2}\}, the image of [2, 2] under the continuous function \sqrt{·} : ℝ^{+} → ℝ^{+}, is something like (1.414213538 ⋯, 1.414213657 ⋯). Moreover, the set of strictly positive real numbers is not representable by means of a closed interval. In summary, we believe that considering a mixture of open, closed, and half-open intervals is generally a good idea.

A useful variation on the theme, which we will exploit later, is to consider a mixture of real and integer-valued intervals.

Definition 78 (Real/integer intervals.) Let İ_{B} be an interval approximation over B. The real/integer interval approximation over I_{B} is given by the carrier

IB^{ℕ} =^{def} I_{B} ∪ { I ∩ ℕ | I ∈ I_{B} }

augmented with all the operators of I_{B}, suitably extended to IB^{ℕ} so to satisfy the requirements of Definition 75. We will use the notation [l .. u], with l, u ∈ B ∩ ℕ for the finite sets in IB^{ℕ}. The remaining elements of { I ∩ ℕ | I ∈ I_{B} } will be denoted by (−∞ .. u], [l .. +∞), and (−∞ .. +∞).

The mathematically inclined might be surprised (or upset) by the fact that we use the word “interval” to denote also non-convex subsets of the reals: we simply find this terminology convenient.

We would now like to give an example explaining why, in Definition 75, we allow for some crudeness on the part of the arithmetic operators. Consider division in İB^{ℕ}. Defining ⧄ in the most precise way, namely as IB^{ℕ} ∘(·/·), we would be able to obtain

[6 .. 6] ⧄ [1 .. 3] =^{def} IB^{ℕ}({ x/y | x ∈ [6 .. 6], y ∈ [1 .. 3] }) = [2 .. 6]. (5.26)

However, one normally uses a more efficient division operator, which avoids the divisibility checks at the price of a small precision penalty. Such an operator would yield the cruder result [6..6]⧄[1..3] = [2, 6], for instance. So we do not want to make any a priori commitment to maximum precision. Nonetheless, we might do wish to be precise. This point gives us an opportunity for a brief digression.

The abstract interpretation practitioner might have protested because we did not present the real/integer interval approximation as a reduced product. Indeed, the carrier of İB^{ℕ} can be obtained as the reduced product between an interval domain and a domain for integrality with two elements: ‘integer’ and ‘don’t know’. But what about the arithmetic operations? Following the reduced product approach, the division operator cannot obtain the same result of (5.26). In fact, [6, 6] divided by [1, 3] gives [2, 6], integer divided by integer yields don’t know, and there is no way for [2, 6] and don’t know to be reduced to [2 .. 6]. This limitation is undesirable, since one might well want to implement (5.26). In summary, our choices (both here and in general) are justified by the desire of retaining maximum freedom in the actual implementations. In this respect, we condemn both excessive commitment to precision and to sloppiness equally.

Of course, the examples of ℝ-approximations we have shown are not exhaustive. One can consider some family of sets of intervals, also called multi-intervals, for instance. The theory of abstract interpretation offers a great variety of possibilities for designing the carrier of the approximations, starting from some base domains, through standard constructions like reduced product, downset completion, auto-dependencies and so forth. This remark clearly applies also to the approximations for cardinalities and binary relations we are about to present.

5.6 Approximations for Cardinalities

For the purpose of representing multiset cardinalities any approximation of ℘(ℕ) satisfying some minimal requisites would do.

Definition 79 (ℕ-approximation.) A ℕ-approximation is any algebra of the form

Ǎ_{ℕ} =^{def} ⟨A_{ℕ}, ⊞, ⊟_{2}⟩,

where

1. A_{ℕ} ⊆ ℘(ℕ) is an abstraction of ℘(ℕ), the corresponding upper closure operator being A_{ℕ} ∈ uco(℘(ℕ), ⊆). Moreover, A_{ℕ} contains ∅, {0}, and ℕ \ {0}.

2. ⊞ and ⊟_{2} are correct approximations of the linear extensions of +, −: ℕ^{2} → ℕ.

Again, we will use the symbols ⊗ and ⊕ to denote the glb (set intersection) and the lub operators, respectively, of the complete lattice A_{ℕ}.

5.6.1 An Example

As an example of ℕ-approximation we introduce now a simple domain of intermediate precision between signs and integer intervals. In lack of a better name, we will refer to this trivial exercise in abstract interpretation as the cardinalities approximation.

                 [figure: ⊥_{c}; 0; 1; 01; 1^{+}; 0^{+}]

Figure 5.7: Hasse diagram of the cardinalities’ lattice.

Definition 80 (Cardinalities.) The cardinality approximation is given by ⟨{⊥_{c}, 0, 1, 01, 1^{+}, 0^{+}}, ⊞, ⊟_{2}⟩, where

⊥_{c} =^{def} ∅,  0 =^{def} {0},  1 =^{def} {1},
01 =^{def} {0, 1},  1^{+} =^{def} ℕ \ {0},  0^{+} =^{def} ℕ.

The complete lattice which arises from this definition is depicted in Figure 5.7, whereas the ⊞ and ⊟_{2} operators are summarized in Tables 5.1 and 5.2 on the next page.

              ⊞ ⊥_{c} 0   1   01  1^{+} 0^{+}

⊥_{c} ⊥_{c} ⊥_{c} ⊥_{c} ⊥_{c} ⊥_{c} ⊥_{c}

              0   ⊥_{c} 0   1   01  1^{+} 0^{+}
              1   ⊥_{c} 1   1^{+} 1^{+} 1^{+} 1^{+}
              01   ⊥_{c} 01  1^{+} 0^{+} 1^{+} 0^{+}

1^{+} ⊥_{c} 1^{+} 1^{+} 1^{+} 1^{+} 1^{+}
0^{+} ⊥_{c} 0^{+} 1^{+} 0^{+} 1^{+} 0^{+}

Table 5.1: The ⊞ operator on cardinalities.

             ⊟_{2} ⊥_{c} 0   1   01  1^{+} 0^{+}

⊥_{c} ⊥_{c} ⊥_{c} ⊥_{c} ⊥_{c} ⊥_{c} ⊥_{c}
0   ⊥_{c} 0   ⊥_{c} 0   ⊥_{c} 0

              1   ⊥_{c} 1   0   01   0   01
              01   ⊥_{c} 01   0   01   0   01

1^{+}  ⊥_{c} 1^{+} 0^{+} 0^{+} 0^{+} 0^{+}
0^{+} ⊥_{c} 0^{+} 0^{+} 0^{+} 0^{+} 0^{+}

Table 5.2: The ⊟_{2} operator on cardinalities: x ⊟ y is at the intersection between the x-th row and the y-th column.

5.7 Approximations for Numerical Relations

The approximations of relations carry qualitative information on numerical values. Since there is an interplay between qualitative and quantitative information, our approximations of relations are parametric with respect to a ℝ-approximation. Besides that the following definition is quite similar to the ones we have already seen, the key principle (sound approximation) being exactly the same.

The operations we need to approximate here are for composing, building, and restricting (or applying) binary relations.

Definition 81 (ℝ^{2}-approximation.) Let A_{ℝ} be any a set approximation. A ℝ^{2}-approximation is any algebra of the form

Ä_{ℝ^{2}} =^{def} ⟨A_{ℝ^{2}}, A_{ℝ}, ⧇, ⊠, ◫⟩,

where

1. A_{ℝ^{2}} ⊆ ℘(ℝ^{2}) is an abstraction of ℘(ℝ^{2}) such that ∅ ∈ A_{ℝ^{2}}. Moreover, R^{−1} ∈ A_{ℝ^{2}} whenever R ∈ A_{ℝ^{2}}. The associated upper closure operator will be denoted by A_{ℝ^{2}}.

2. ⊠: A_{ℝ} × A_{ℝ} → A_{ℝ^{2}} is such that, for each A_{1}, A_{2} ∈ A_{ℝ},

A_{1} ⊠ A_{2} ⊇ A_{1} × A_{2}.

3. ⧇: A_{ℝ^{2}} × A_{ℝ^{2}} → A_{ℝ^{2}} is such that, for each R_{1}, R_{2} ∈ A_{ℝ^{2}},

R_{1} ⧇ R_{2} ⊇ R_{1} ∘ R_{2}.

4. ◫: A_{ℝ^{2}} × A_{ℝ} → A_{ℝ} is such that, for each R ∈ A_{ℝ^{2}} and each A ∈ A_{ℝ},

R ◫ A ⊇ R(A).

The ◫ operation is called refinement. Again, we will use the symbols ⊗ and ⊕ to denote the glb (set intersection) and the lub operators, respectively, of the complete lattice A_{ℝ^{2}}.

We now show three representative classes of ℝ^{2}-approximation. As usual, many other alternatives exist.

5.7.1 Ordering Relationships

One of the simplest form of ℝ^{2}-approximation is constituted by ordering relationships.

Definition 82 (Ordering relationships.) The set

O =^{def} {∅, =, <, >, ≤, ≥, ≠, ℝ^{2}},

where

= =^{def} { (x, y) ∈ ℝ^{2} | x = y }, ≠ =^{def} { (x, y) ∈ ℝ^{2} | x ≠ y },
< =^{def} { (x, y) ∈ ℝ^{2} | x < y }, ≤ =^{def} { (x, y) ∈ ℝ^{2} | x ≤ y },
> =^{def} { (x, y) ∈ ℝ^{2} | x > y }, ≥ =^{def} { (x, y) ∈ ℝ^{2} | x ≥ y },

is the carrier of a family of ℝ^{2}-approximations which we will collectively refer to as ordering relationships. The ⊗ and ⊕ operators are given by set-theoretic intersection and union, respectively. Thus, O has the simple lattice structure depicted in Figure 5.8. Let Ȧ_{ℝ} be any ℝ-approximation. The ⊠ and ◫ operators (which depend on Ȧ_{ℝ}) are chosen freely, as long as the requirements of Definition 81 are satisfied. The most precise composition operator, namely O ∘(· ∘ ·), is so simple that it constitutes a compulsory choice. The resulting ⧇ operator is shown in Table 5.3. Any ℝ^{2}-approximation so obtained will be denoted by Ö(Ȧ_{ℝ}) and called ordering relationships over Ȧ_{ℝ}.

[figure: ∅; <; =; >; ≤; ≠; ≥; ℝ^{2}]

Figure 5.8: Hasse diagram of the ordering relationships lattice.

\begin{aligned}⧇ & < & ≤ & > & ≥ & = & ≠ \\ < & < & < & ℝ^{2} & ℝ^{2} & < & ℝ^{2} \\ ≤ & < & ≤ & ℝ^{2} & ℝ^{2} & ≤ & ℝ^{2} \\ > & ℝ^{2} & ℝ^{2} & > & > & > & ℝ^{2} \\ ≥ & ℝ^{2} & ℝ^{2} & > & ≥ & ≥ & ℝ^{2} \\ = & < & ≤ & > & ≥ & = & ≠ \\ ≠ & ℝ^{2} & ℝ^{2} & ℝ^{2} & ℝ^{2} & ≠ & ℝ^{2} \end{aligned}

Table 5.3: The ⧇ operator on ordering relationships.

5.7.2 Bounded Differences

The next ℝ^{2}-approximation we consider comes (as it is the case for many other things in this chapter) from the field of Artificial Intelligence. It allows to express the relative values of two quantities by means of constraints of the form x − y ∈ S, with S ⊆ ℝ, whence the name bounded differences [Dav87]. In AI these approximations proved to be particularly useful for systems which need to place events on a time-line, such as TMM [Dea85] and other planning programs [Ver83]. In our particular formal treatment, any ℝ-approximation induces a system of bounded differences.

Definition 83 (Bounded differences.) Let Ȧ_{ℝ} be an ℝ-approx-imation. Consider, for each A ∈ A_{ℝ}, the set

d(A) =^{def} { (x, y) ∈ ℝ^{2} | (x − y) ∈ A },

and then

D(A_{ℝ}) =^{def} { d(A) | A ∈ A_{ℝ} }.

By construction, A_{ℝ} is isomorphic to D(A_{ℝ}), the isomorphism being given by d: A_{ℝ} → D(A_{ℝ}). In particular, d(∅) = ∅ and d(ℝ) = ℝ^{2}. Moreover, several operations over bounded differences are defined in terms of the operations of Ȧ_{ℝ}. Thus, for each A_{1}, A_{2} ∈ A_{ℝ}:

d(A_{1}) ⊗ d(A_{2}) =^{def} d(A_{1} ⊗ A_{2}),
d(A_{1}) ⊕ d(A_{2}) =^{def} d(A_{1} ⊕ A_{2}),
d(A_{1}) ⧇ d(A_{2}) =^{def} d(A_{1} ⊞ A_{2}),
A_{1} ⊠ A_{2} =^{def} d(A_{1} ⊟_{2} A_{2}),

If ◫ is a refinement operator satisfying Definition 81, it can be shown that

D̈(Ȧ_{ℝ}) =^{def} ⟨D(A_{ℝ}), A_{ℝ}, ⧇, ⊠, ◫⟩

is indeed an ℝ^{2}-approximation. We will call such an approximation bounded differences over Ȧ_{ℝ}.

Observe that a plausible definitions of the refinement operator, in terms of A_{ℝ} operators, is the following:

d(A_{1}) ◫ A_{2} =^{def} A_{2} ⊟_{2} A_{1},

where A_{1}, A_{2} ∈ A_{ℝ}. However, generally speaking, this does not automatically define neither the most precise refinement operator, nor an efficient one. Of course, the more precise is the ℝ-approximation which is used as the basis, the more precise is the resulting system of bounded differences. Observe also that Definition 83 is somewhat unnecessarily restrictive, in that it assumes that one uses the same ℝ-approximation both per se (namely, as a way for approximating sets of values) and as the basis for the bounded differences construction. Of course, one can make a different choice: for example using intervals for bounded differences together with, say, some family of multi-intervals.

As a final remark, notice that any conventional interval approximation, such as the one introduced in Definition 77, is not strong enough in order to obtain, through the bounded-differences construction, an ℝ^{2}-approximation capturing all the ordering relationships of Ö. In fact, while any decent interval approximation İ_{B} is such that D(İ_{B}) can represent =, <, >, ≤, and ≥ (by means of [0, 0], (−∞, 0), (0, +∞), (−∞, 0], and [0, +∞), respectively), it cannot represent ≠. Indeed, ≠ would correspond to (−∞, 0)∪(0, +∞), which usually does not belong to I_{B}. The solution is to adopt an ℝ-approximation which is able to capture constraints of the kind x ≠ 0. A suitable variation on the intervals theme is given by

IB^{≠0} =^{def} I_{B} ∪ { I \ {0} | I ∈ I_{B} },  (5.27)

which, if endowed with reasonable ⊞ and ⊟_{2} operators, is such that D̈(İB^{≠0}) is strictly more precise than Ö, that is, O ⊂ D(IB^{≠0}).

5.7.3 Bounded Quotients

Consider the extended reals ℝ_{∞} =^{def} ℝ ∪ {−∞, +∞}, with the arithmetic operations extended in such a way that, for each b ∈ ℝ with b > 1,

\frac{0}{0} =^{def} 1,  \frac{x}{0} =^{def} +∞, for each x > 0,
log_{b} 0 =^{def} −∞,  log_{b}(+∞) =^{def} +∞,
(+∞) − (+∞) =^{def} 0,  (−∞) − (−∞) =^{def} 0.

This is what is needed to ensure that, for each x, y ∈ ℝ_{∞},

log_{b} |\frac{x}{y}| = log_{b}|x| − log_{b}|y|.

Once one has fixed a base b > 1, a system of bounded quotients captures relations of the form log_{b}|x| − log_{b}|y| ∈ S. As such, it is quite similar to a system of bounded differences. In the field of Artificial Intelligence, systems of bounded differences, restricted to positive quantities, have been used for temporal reasoning [AK85]. In our case quantities can be negative, whence the need for taking the absolute value, or can be zero, which is why we need to consider the extended structure of the reals.

Definition 84 (Bounded quotients.) Let us fix b ∈ ℝ such that b > 1, and let Ȧ_{ℝ_{∞}} and Ȧ_{ℝ} be an ℝ_{∞}-approximation and an ℝapproximation, respectively. Consider the function q: A_{ℝ_{∞}} → ℝ^{2} given, for each A ∈ A_{ℝ_{∞}}, by

q(A) =^{def} { (x, y) ∈ ℝ^{2} | log_{b}|x| − log_{b}|y| ∈ A }.

Then, on the set Q(A_{ℝ_{∞}}) =^{def} { q(A) | A ∈ A_{ℝ_{∞}} }, we consider the following operations, for each A_{1}, A_{2} ∈ A_{ℝ_{∞}}:

q(A_{1}) ⊗ q(A_{2}) =^{def} q(A_{1} ⊗ A_{2}),
q(A_{1}) ⊕ q(A_{2}) =^{def} q(A_{1} ⊕ A_{2}),
q(A_{1}) ⧇ q(A_{2}) =^{def} q(A_{1} ⊞ A_{2}).

If we are also given two functions ⊠: A_{ℝ} × A_{ℝ} → Q(A_{ℝ_{∞}}) and ◫: Q(A_{ℝ_{∞}}) × A_{ℝ} → A_{ℝ} such that

A_{1} ⊠ A_{2} ⊇ A_{1} × A_{2},
q(A_{1}) ◫ A_{2} ⊇ { y ∈ ℝ | ∃x ∈ A_{2} . ∃z ∈ A_{1} . y = ±b^{−z}|x| },

then Q̈(Ȧ_{ℝ_{∞}}, Ȧ_{ℝ}) =^{def} ⟨Q(A_{ℝ_{∞}}), A_{ℝ}, ⧇, ⊠, ◫⟩, called bounded quotients over Ȧ_{ℝ_{∞}} and Ȧ_{ℝ}, is an ℝ^{2}-approximation.

5.8 Approximations are Constraints

As we have seen, our family of domains for range and relations analysis has, by now, three degrees of freedom. Once we have chosen an ℝ-approximation, A_{ℝ}, an ℕ-approximation, A_{ℕ}, and an ℝ^{2}- approximation, A_{ℝ^{2}}, our base domain is the complete lattice

D_{n}^{♯} =^{def} D_{n}^{♯}(A_{ℝ}, A_{ℕ}, A_{ℝ^{2}}) =^{def} A_{ℝ}^{n} × A_{ℕ}^{n} × A_{ℝ^{2}}^{n(n−1)/2}  (5.28)

The A_{ℕ} component carries information on multiset cardinality, A_{ℝ} conveys quantitative information on the values of multisets’ elements, whereas A_{ℝ^{2}} is for qualitative information on that values.

One way to obtain a description in D_{n}^{♯} for a predicate p in a program P, would be to execute all the possible computation paths of P, collecting all the tuples of ground terms that represent the possible successes of p. If the set of all tuples is T̂ and we take a common anti-instance s of T̂ with n variables, we know how, by means of the sequence of abstractions above, to obtain the desired element of D_{n}^{♯}. This, of course, is impossible, in general.

Something else must be done, and the domain D_{n}^{♯} turns out to be too weak for our purposes. Before discussing this topic, we better equip ourselves with a language that will make easier talking about the elements of D_{n}^{♯}.

We can think about each element of D_{n}^{♯} as a safe description of an n-tuple of multisets that is, in general, unknown. Let us call this tuple that we have in mind X̂. It is important to understand that the tuple X̂ is defined elsewhere: here X̂ is just a variable symbol standing for a particular, unspecified tuple of multisets of numbers. When our approximations will be put at work each X̂ will be given a precise definition, like the following:

X̂ =^{def}( nl^{+}(t_{1}), …, nl^{+}(t_{n}))

for some (t_{1}, …, t_{n}) belonging to an unknown set T̂. The set T̂, though entirely defined^{14}, is unknown, so is (t_{1}, …, t_{n}), and so will be X̂. After all, the objective of the game is to derive some information about what T̂ looks like.

14For instance, “T̂ is the set of n-tuples of terms to which a certain n-tuple of variables can be bound when the computation reaches a particular control point following one of some set of computation paths assuming that the resulting constraint store is consistent.”

So, X̂ is a variable ranging over n-tuples of multisets. What we will do is to put constraints on X̂ in order to restrict the set of n-tuples of multisets in its range. By doing this we will indirectly constrain the tuple (t_{1}, …, t_{n}) and thus T̂ itself.

What is needed is a language for constraining X̂ based on our approximations for cardinalities, sets, and relations. For instance, we could write

ζ(π_{1}(X̂)) ⊆ [1, 3]  (5.29)

meaning that X can stand for any n-tuple of multisets of the reals such that the elements of the first multiset are all included in [1, 3]. We can use any element of a chosen ℝ-approximation instead of [1, 3]. If we write also

∥π_{1}(X̂)∥ ∈ {1, 5}  (5.30)

we further specify that the first multiset must have cardinality 1 or 5. Once we have fixed an ℕ-approximation we have a language for expressing sets of cardinalities. Finally, we might also specify that

ζ(π_{1}(X̂)) × ζ(π_{2}(X̂)) ⊆ { (x, y) ∈ ℝ^{2} | x < y },  (5.31)

thus saying that the elements of the first multiset are less than those (if any) of the second multiset. Any ℝ^{2}-approximation provides us with a language for specifying binary relations.

The notation used in (5.29)–(5.31) is quite cumbersome. We will use the following equivalent formulation instead:

X̂_{1}^{q} ⊆ [1, 3],  instead of (5.29);
X̂_{1}^{c} ⊆ {1, 5},  instead of (5.30);
X̂_{1,2}^{r} ⊆ { (x, y) ∈ ℝ^{2} | x < y },  instead of (5.31).

What we have done can be thought as if we had “exploded” the variable symbol X̂ into several variable symbols. These are, for each i, j ∈ {1, …, n},

X̂_{i}^{c}, X̂_{i}^{q}, X̂_{ij}^{r}.

Suppose X̂ stands for the tuple

(M_{1}, …, M_{n}),

then

• X̂_{i}^{c} stands for the singleton set of natural numbers containing the cardinality of M_{i}, that is {∥M_{i}∥};

• X̂_{i}^{q} stands for the set of values occurring in M_{i}, namely ζ(M_{i});

• X̂_{ij}^{r} stands for ζ(M_{i}) × ζ(M_{j}).

It is easy to associate each element of D_{n}^{♯} to a set of constraints of the form

v ⊆ K,

where v is any of X̂_{i}^{c}, X̂_{i}^{q}, or X̂_{ij}^{r}, and K is an element of the corresponding ℕ-, ℝ-, or ℝ^{2}-approximation. On the other hand, to each set of constraints of this form corresponds an element of D_{n}^{♯}. However, with our syntax we can express more than this. We can say, for instance,

X̂_{i}^{q} ⊆ X̂_{j}^{q},

meaning that the i-th multiset does not contain more numeric values than those occurring in the j-th multiset. As we will see, we are moving to an abstract domain that is much more powerful than D_{n}^{♯}: a space of kernel operators over D_{n}^{♯}. For this purpose, we will do the following:

1. define a class of expressions over our special variable symbols and approximations;

2. define a class of basic constraints (or tokens) based on these expressions;

3. define a class of entailment relations between the basic constraints;

4. define a class of determinate constraint systems: as seen in Chapter 3, these are induced by the simple constraint system constituted by a set of basic constraints together with a suitable entailment relation and merge operator;

5. define ask-and-tell constraint systems over these d.c.s.;

6. use finite cc agents over the above ask-and-tell c.s. in order to capture the relevant aspects of concrete computations.

Let us start with expressions. The punctilious reader will forgive us if we do not use different symbols for the syntactic operators and the corresponding semantic operations.

Definition 85 (Abstract expressions.) The set of cardinality expressions, E_{c}, is the language generated by the following grammar:

c ::= N  with N ⊆ A_{ℕ}
| X̂_{i}^{c}  with i ∈ {1, …, n}
| c_{1} ⊗ c_{2}  with c_{1}, c_{2} ∈ E_{c}
| c_{1} ⊕ c_{2}
| c_{1} ⊞ c_{2}
| c_{1} ⊟_{2} c_{2}

Similarly, the sets of quantity expressions, E_{q} ∋ q, and relationship expressions, E_{r} ∋ r, are given by

q ::= S  with S ⊆ A_{ℝ}
| X̂_{i}^{q}  with i ∈ {1, …, n}
| q_{1} ⊗ q_{2}  with q_{1}, q_{2} ∈ E_{q}

| q_{1} ⊕ q_{2}

| r ◫ q  with r ∈ E_{r}

| q_{1} ⊞ q_{2} | ⊟_{1} q | q_{1} ⊟_{2} q_{2} | q_{1} ⧆ q_{2} | q_{1} ⧄ q_{2} | sin q

⋮  (all the other operators)

and

r ::= R  with R ⊆ A_{ℝ^{2}}
| X̂_{ij}^{r}  with i, j ∈ {1, …, n} and i ≠ j

| r^{−1}

| r_{1} ⊗ r_{2}  with r_{1}, r_{2} ∈ E_{r}

| r_{1} ⊕ r_{2} | r_{1} ⧇ r_{2}

| q_{1} ⊠ q_{2}  with q_{1}, q_{2} ∈ E_{q}

The set of all such expressions is denoted by

E =^{def} E_{c} ∪ E_{q} ∪ E_{r}.  (5.32)

We will denote by E_{0} the set of constant expressions, namely, the subset of E containing all the expressions where no variable symbols occur.

In what follows we will always assume that everything is “welltyped”. Thus, when we write e_{1} ⊗ e_{2} and e_{1} ∈ E_{q} we are implicitly stating that e_{2} ∈ E_{q}. In order to simplify the exposition we also introduce the set

A =^{def} A_{ℕ} ∪ A_{ℝ} ∪ A_{ℝ^{2}}  (5.33)

that will be used freely under the assumption of well-typedness. So, for K_{1}, K_{2} ∈ A, writing K_{1} ⊆ K_{2} implies that K_{1} and K_{2} belong to the same set, among the three that are united in (5.33).

Needless to say, constant expressions can be evaluated in the obvious way.

Definition 86 (Evaluation of expressions.) The function

eval: E_{0} → A

is defined, by structural induction, in the obvious way. For instance, for each e_{1}, e_{2} ∈ E_{0},

eval(K) =^{def} K,  if K ∈ A;
eval(e_{1} ⊗ e_{2}) =^{def} eval(e_{1}) ⊗ eval(e_{2}),
eval(e_{1} ⊕ e_{2}) =^{def} eval(e_{1}) ⊕ eval(e_{2}),
eval(e_{1}^{−1}) =^{def} eval(e_{1})^{−1}
eval(e_{1} ⊞ e_{2}) =^{def} eval(e_{1}) ⊞ eval(e_{2}),
…

Expressions need also to be manipulated syntactically. The (usually limited) algebraic capabilities of the system are encoded by a relation.

Definition 87 (Safe approximation.) A relation ⋞ ⊆ E × E is a safe approximation relation if

K_{1} ⋞ K_{2},  for each K_{1}, K_{2} ∈ A such that K_{1} ⊆ K_{2};
e_{1} ⋞ e_{1} ⊕ e_{2}, for each e_{1}, e_{2} ∈ E.

Moreover, whenever

e_{1} ⋞ e_{2}

if vars((e_{1}, e_{2})) = {v_{1}, …, v_{k}} and K_{i} ∈ A is a constant of the same type of v_{i}, with i = 1, …, k, then

eval(e_{1}[K_{1}/v_{1}, …, K_{k}/v_{k}]) ⊆ eval(e_{2}[K_{1}/v_{1}, …, K_{k}/v_{k}]).

By the first condition the system is able to relate constants: a very minimal requirement. The second condition will be needed to define a sensible merge operator. The last condition, instead, is a guarantee of soundness: if e_{1} ⋞ e_{2} the functions f_{e_{1}}, f_{e_{2}} : A^{k} → A, encoded by e_{1} and e_{2} through eval and substitution, are such that f_{e_{1}} ⊆ f_{e_{2}}.

Definition 88 (Abstract constraints.) An abstract numeric constraint is any formula taking one of the following forms:

X̂_{i}^{c} ⊆ e_{c},  with e_{c} ∈ E_{c};
X̂_{i}^{q} ⊆ e_{q},  with e_{q} ∈ E_{q};
X̂_{ij}^{r} ⊆ e_{c},  with e_{r} ∈ E_{r}.

Constraints not containing any variable symbol on their left-hand side are called basic constraints. We assume that the left-hand sides of basic constraints consist of a single constant (namely, we assume that constant folding is always performed). The set of all the abstract constraints is denoted by C.

The entailment relation is parametric with respect to a safe approximation relation.

Definition 89 (Abstract entailment.) Let ⋞ be a safe approximation relation. The entailment relation over ⋞, ⊢ ⊆ ℘_{f}(C) × C is the minimal relation satisfying all the conditions of Definition 23 on page 64, plus the the following axiom schemata, where v (possibly subscripted) is any of X̂_{i}^{c}, X̂_{i}^{q}, or X̂_{ij}^{r}, for i and j in {1, …, n} with i ≠ j. Constraints can be wakened:

{v ⊆ e_{1}} ⊢ v ⊆ e_{2},  if e_{1} ⋞ e_{2}.  (5.34)

Constraints can be combined: by “conjuncting” expressions,

{v ⊆ e_{1}, v ⊆ e_{2}} ⊢ v ⊆ e_{1} ⊗ e_{2},  (5.35)

and by substituting expressions for some variables:

{ v ⊆ e ; v_{1} ⊆ e_{1} ; ⋮ ; v_{s} ⊆ e_{s} } ⊢ v ⊆ e[e_{1}/v_{1}, …, e_{k}/v_{k}].  (5.36)

Here the expression e_{i} has the same type of v_{i}, for each i = 1, …, s. Information is propagated from cardinalities to quantities,

{ X̂_{i}^{c} ⊆ {0} } ⊢ X̂_{i}^{q} ⊆ ∅,  (5.37)

and back,

{ X̂_{i}^{q} ⊆ ∅ } ⊢ X̂_{i}^{c} ⊆ {0}.  (5.38)

Unsatisfiability is detected and globalized: for cardinalities,

{ X̂_{i}^{c} ⊆ ∅ } ⊢ ⊥,  (5.39)

and for relations,

\{ \begin{matrix}X̂_{ij}^{r} & ⊆ & ∅ \\ X̂_{i}^{c} & ⊆ & ℕ \ \{0\} \\ X̂_{j}^{c} & ⊆ & ℕ \ \{0\} \end{matrix} \} ⊢ ⊥.  (5.40)

The justification behind (5.39) is that a multiset must have a cardinality. Thus X̂_{i}^{c} ⊆ ∅ indicates an inconsistency. For (5.40), two sets of number are in the empty relation if and only if one or the other is empty. Thus, if both are non-empty the overall system is inconsistent.

By Definition 89 the structure (C, ⊢) is a simple constraint system. It is thus natural to apply the determinate constraint system construction described in Definition 26 on page 75.

Definition 90 The constraint system J is the determinate constraint system built over (C, ⊢) with set intersection as the merge operator.

An important observation is that the role of set intersection as a merge operator is controlled by the safe approximation relation ⋞ that has been chosen. Suppose we have two abstract constraints C′, C′′ ∈ J such that

C′ ⊃ {X̂_{1}^{q} ⊆ e′, X̂_{2}^{q} ⊆ X̂_{3}^{q} ⊞ X̂_{4}^{q}},
C′′ ⊃ {X̂_{1}^{q} ⊆ e′′, X̂_{2}^{q} ⊆ X̂_{4}^{q} ⊞ X̂_{3}^{q}}.

Given the minimal requirements we have imposed on ⋞, we know, since C′ and C′′ are closed under entailment, that

C′ ∩ C′′ ⊃ {X̂_{1}^{q} ⊆ e′ ⊕ e′′, X̂_{2}^{q} ⊆ (X̂_{3}^{q} ⊞ X̂_{4}^{q}) ⊕ (X̂_{4}^{q} ⊞ X̂_{3}^{q})}.

Even if ⊞ is commutative, without the ⋞ relation being such that

X̂_{i}^{q} ⊞ X̂_{j}^{q} ⋞ X̂_{j}^{q} ⊞ X̂_{i}^{q}

we cannot have

C′ ∩ C′′ ⊃ {X̂_{2}^{q} ⊆ X̂_{3}^{q} ⊞ X̂_{4}^{q}}.

This is how the algebraic capabilities of the system are reflected into the merge operation.

As we will see, the J constraint system is still not strong enough for our purposes. We thus define finite cc agents over J as in Section 3.6 on page 87, and move to an ask-and-tell constraint system,

Definition 91 The constraint system K is the ask-and-tell constraint system built over J together with a suitable ask-and-tell merge operator, as in Definition 44 on page 95.

For the choice of the merge operator some possibilities are described in Section 3.6.1 on page 97.

The ask-and-tell abstract domain K is quite expressive. Now the question is how do we use it? Or, in other words, where do the agents come from? The answer is that there are a variety of agents that come from different sources and serve different purposes:

implicit agents: agents expressing consistency conditions on X̂;

numeric agents: agents that arise from concrete numeric expressions or are abstractions of concrete numeric constraints:

binding agents: agents that express parameter passing;

remapping agents: agents that arise in remapping;

The next few sections are devoted to describing them.

5.9 Implicit Agents

As the name says it, implicit agents do not arise as abstraction of concrete constraints or operations. Rather, they encode consistency conditions on the various pieces of information that are carried by abstract constraints. These conditions, which are based on quantitative and qualitative arithmetic reasoning, allow to approximate the reduced product of the three components we started with: A_{ℕ}, A_{ℝ}, and A_{ℝ^{2}}.

5.9.1 Transitive Closure

A purely qualitative inference technique is known under the historical name of transitive closure [All83, Sim86]. It performs the approximate composition of known relationships in order to infer new relationships. Formally speaking, transitive closure is justified by the following inference rule, where A, B, C ∈ ℘^{+}(ℝ) and R_{1}, R_{2} ∈ ℝ^{2}:

\begin{matrix}∀x A : ∀y B : x R_{1} y \\ ∀y B : ∀z C : y R_{2} z \\ B ≠ ∅ \end{matrix} ; ∀x A : ∀z C : x (R_{1} ∘ R_{2}) z  (5.41)

Observe that the premise B ≠ ∅, ensuring the existence of at least one “pivot element”, is essential for the soundness of the inference. Rule (5.41) is approximated by the agents

ask(X̂_{j}^{c} ⊆ ℕ \ {0}) → tell(X̂_{ik}^{r} ⊆ X̂_{jk}^{r} ⧇ X̂_{ij}^{r}),  (5.42)

for each {i, j, k} ⊆ {1, …, n}.

Examples of inferences are the following, assuming that B is non-empty):

• A < C from A ≤ B and B < C, if ordering relationships are adopted;

• A − C ∈ [0, 1] from A − B = −1 and B − C ∈ [1, 2], when A_{ℝ^{2}} is a system of bounded differences^{15};

• |A/C| ∈ [1, e] from |A/B| = e^{−1} and |B/C| ∈ [e, e^{2}], employing bounded quotients^{16}.

5.9.2 Quantity Refinement

The technique we call quantity refinement allows for the inference of quantitative information from qualitative information. Quantity refinement applies an approximation of the inference rule

∀x A : ∀y B : x R y  A ≠ ∅ ; ζ(B) ⊆ R(ζ(A))  (5.43)

Again, the premise about non-emptiness is crucial. In fact, whenever A = ∅, the first premise of (5.43) holds vacuously for any relation R ⊆ ℝ^{2}. This, clearly, does not allow to conclude that B = ∅, since R(∅) = ∅.

The repeated application of the inference rule (5.43) is an instance of network consistency technique [Mon74, Mac77, Fre78]. Such techniques consist in removing from the domains of network nodes (quantity labels, in our case) values that cannot appear in a solution, or, differently stated, that do not satisfy the global constraint represented by the network. More precisely, (5.43) expresses an arc-consistency step. All the arc-consistency techniques are based on an observation, due to Fikes [Fik70, Mac77], that can be rephrased as follows:

15For instance, take A_{ℝ^{2}} to be D(I_{B}) where the boundaries on which I_{B} is based include −1, 0, 1, and 2.

16For instance, Q(I_{B}) where I_{B} is as in the previous example and assuming natural logarithms.

Let u and v be two variables whose domains are D_{u} and D_{v}, respectively, and suppose we know that a certain relation R holds between u and v: formally we have the constraints u ∈ D_{u}, v ∈ D_{v}, and u R v. If x ∈ D_{u} and there is no y ∈ D_{v} such that (x, y) ∈ R, then we can replace D_{u} with D′_{u} = D_{u} \ {x} in the above set of constraints without changing its semantics. When we have gone through this process for each x ∈ D_{u}, ending up with

D_{u}^{∗} =^{def} D_{u} \ { x ∈ D_{u} | ∄y ∈ D_{v} . (x, y) ∈ R },

then the arc (u, v) (or, better, the relation R between u and v) is consistent with D_{u}^{∗} and D_{v}. Consistency of the arc (v, u) is enforced in the same way, swapping u for v and considering R^{−1} instead of R.

In our setting, the approximation of rule (5.43) is given by the agents

ask(X̂_{i}^{c} ⊆ ℕ \ {0}) → tell(X̂_{j}^{q} ⊆ X̂_{ij}^{r} ◫ X̂_{i}^{c}),  (5.44)

for each i, j = 1, …, n with i ≠ j.

As an example of inference, let us assume that D_{n}^{♯} is based on IB^{ℕ} and O. Then, from A > B, B non-empty, A ∈ [1..6], and B ∈ [4, 9], quantity refinement yields both A ∈ [5 .. 6] and B ∈ [4, 6). In general, when using intervals and ordering relationships, knowing the relation between A and B may allow to refine the intervals associated to A and B. It must be clear that (5.44) is only an approximation of an arc-consistency step. In fact, there is no a priori guarantee that the ℝ-approximation employed is expressive enough to effectively remove all the “impossible values”, given a particular choice of ℝ^{2}-approximation. In the setting of intervals with ordering relationships this phenomenon can be appreciated by considering that the approximate refinement rule (5.44) cannot deduce anything, in any case, from A ∈ [2 .. 2], B ∈ [1, 3], and A ≠ B.

5.9.3 Numeric Constraint Propagation

As quantity refinement exploit qualitative knowledge in order to obtain quantitative information, it is also possible to go the other way around. In [Sim86] an instance of this this process was named numeric constraint propagation. This technique consists in determining the relationship between two quantities when enough qualitative information is available. Formally, it is as simple as

∀x A : ∀y B : x (ζ(A) × ζ(B)) y  (5.45)

which is then approximated by the agents

tell(X̂_{ij}^{r} ⊆ X̂_{i}^{q} ⊠ X̂_{j}^{q}),  (5.46)

for each i, j = 1, …, n with i ≠ j.

When using intervals and ordering relationships, numeric constraint propagation consists in determining the relationship between two quantities when their associated intervals do not overlap, except possibly at their endpoints. For example, if A ∈ (−∞, 2], B ∈ [2, +∞), and C ∈ [5, 10], we can infer that A ≤ B and A < C.

5.10 Numeric Agents

Numeric agents result from the abstract compilation of concrete clauses. For a rough description of the essence of this process, we help ourselves with the following two clauses, excerpted from the fill_rectangle program of Figure 4.2 on page 123.

fill([Y0,X1,Y1|L],[[X1,Y1,B]|C],L3,C3) :-    % clause 1
    Y0 > Y1,
    Y1 + B <= 1,
    place_square([X1,Y1|L],[X1 + B|L1]),
    fill([Y1 + B,X1 + B|L1],C,L2,C2),
    fill([Y0,X1|L2],C2,L3,C3).
place_square([_,Y1,X2,Y2|L],L1) :-           % clause 2
    Y1 = Y2,
    place_square([X2,Y2|L],L1).

The first thing to do is to assign to each non-anonymous program variable and to each numeric expression a quantity. A quantity is characterized by its formula and its index: a position within the tuple of multisets X̂. We will denote by ⟨f⟩_{i} the quantity corresponding to the formula f, which has been assigned the index i. Thus, the first step of the compilation is to assign the quantities

{⟨Y0⟩_{1}, ⟨X1⟩_{2}, ⟨Y1⟩_{3}, ⟨L⟩_{4}, ⟨B⟩_{5}, ⟨C⟩_{6}, ⟨L3⟩_{7}, ⟨C3⟩_{8}, ⟨Y1 + B⟩_{9}, ⟨X1 + B⟩_{10}, ⟨L1⟩_{11}, ⟨L2⟩_{12}, ⟨C2⟩_{13}} (5.47)

to clause 1, and the quantities

{⟨Y1⟩_{1}, ⟨X2⟩_{2}, ⟨Y2⟩_{3}, ⟨L⟩_{4}, ⟨L1⟩_{5}}  (5.48)

to clause 2. It is important to remark that, as far as the compilation process is concerned, clauses are considered in isolation, one at a time, and no information is propagated from one clause to another.

Clause 1 is thus associated to a 13-tuple of multisets of numbers, one for each of its quantities. So, when dealing with clause 1, X̂_{1}^{c} is the cardinality of the multiset of numbers that might appear in the term bound to Y0 just before clause 1 terminates its execution with success, having followed one particular computation path. Similarly, X̂_{4}^{q} is the set of numbers that might occur in the term bound to L etc.

5.10.1 Cardinality Agents

The next step in the compilation process is to examine the constraints appearing in the clause and the quantities’ formulas in order to identify numeric variables. Variables appearing either on a numeric constraints, or in a numeric expression are certainly numeric. In other words, no computation of the clause can succeed if they are bound to a non-numeric term. Notice that also variables appearing in aliasing constraints, like X = Y, can be classified as numeric, if X or Y has been recognized as such. Thus, this local analysis requires a small fixpoint computation, or, if you prefer, the computation of a transitive closure. Quantities whose formula contains a numeric variable are classified as numeric.

For instance, in clause 1 of our small example, the quantities

⟨Y0⟩_{1}, ⟨X1⟩_{2}, ⟨Y1⟩_{3}, ⟨B⟩_{5}, ⟨Y1 + B⟩_{9}, ⟨X1 + B⟩_{10},

are recognized as numeric, whereas no numeric quantities can be detected in clause 2. Now, for each numeric quantity we produce an agent of the form

X̂_{i}^{c} ⊆ A_{ℕ}({1}),  (5.49)

where i corresponds to the index of that quantity. This says, with the precision allowed by A_{ℕ}, that the cardinality of our i-th multiset is 1, or, at the very least, that the i-th multiset is not empty.

As far as clause 2 is concerned, notice that the aliasing constraint Y1 = Y2 does not say anything about the “type” of Y1 and Y2. The fact that in any computation starting from the main program predicate (fill_rectangle/3, see Figure 4.2 on page 123) the constraint Y1 = Y2 will play the role of a numeric equation is, of course, irrelevant.^{17} Nonetheless, the constraint Y1 = Y2 does give rise to a couple of cardinality agents. With reference to the set of quantities given in (5.48), they are

17There are, however, CLP languages providing “typed” equations.

X̂_{1}^{c} ⊆ X̂_{3}^{c}  and  (5.50)
X̂_{3}^{c} ⊆ X̂_{1}^{c}.  (5.51)

Whatever term Y1 stands for, Y2 stands for the same thing: this fact propagates to the cardinalities of the multisets of numbers that might be present in this term.

5.10.2 Constraint Agents

Constraint agents arise from a straightforward analysis of the constraints appearing in program clauses. With reference to the set of quantities (5.47), for clause 1 we get

X̂_{1,3}^{r} ⊆ A_{ℝ^{2}}({ (x, y) ∈ ℝ^{2} | x > y }),  (5.52)

from Y0 > Y1, and

X̂_{9}^{q} ⊆ A_{ℝ}((−∞, 1]),  (5.53)

from Y1 + B <= 1. For clause 2, instead, we have the constraint agent

X̂_{1,3}^{r} ⊆ A_{ℝ^{2}}({ (x, x) ∈ ℝ^{2} }).  (5.54)

This shows us that it is always a good idea to employ an ℝ^{2}- approximation at least as strong as ordering relationships. This implies that bounded quotients approximation should not be used alone, but, for instance, in some kind of product with ordering relationships themselves.

When A_{ℝ^{2}} does contain ordering relationships,^{18} the constraint agents for clause 1

X̂_{1}^{q} ⊆ (>) ◫ X̂_{3}^{q}  and  (5.55)
X̂_{3}^{q} ⊆ (<) ◫ X̂_{1}^{q},  (5.56)

that constitute a sound approximation of Y0 > Y1, are superfluous. In fact, they are subsumed by the combination of the quantity refinement agents

ask(X̂_{3}^{c} ⊆ ℕ \ {0}) → tell(X̂_{1}^{q} ⊆ X̂_{3,1}^{r} ◫ X̂_{3}^{c})  and
ask(X̂_{1}^{c} ⊆ ℕ \ {0}) → tell(X̂_{3}^{q} ⊆ X̂_{1,3}^{r} ◫ X̂_{1}^{c}),

the cardinality agents

X̂_{1}^{c} ⊆ A_{ℕ}({1})  and
X̂_{3}^{c} ⊆ A_{ℕ}({1}),

and the constraint agent (5.52). Indeed, the above combination is stronger than (5.55) and (5.56). In the latter the relationships are given by constant relation expressions, and thus they cannot be strengthened. In the combination, instead, the relation expression X̂_{1,3}^{r} can be strengthened by effect of other agents (think about using bounded differences, or using ordering relationships supposing we had Y0 >= Y1 instead of Y0 > Y1).

A similar argument holds for clause 2 too, where one might be tempted to produce the agents

X̂_{1}^{q} ⊆ X̂_{3}^{q}  and  (5.57)
X̂_{3}^{q} ⊆ X̂_{1}^{q}  (5.58)

18As stated in Definition 82 on page 195, < =^{def} { (x, y) ∈ ℝ^{2} | x < y } and likewise for the other ordering relationships.

from the concrete constraint Y1 = Y2. Such agents will never be needed when using ordering relationships: an assumption that, from now on, we will take for granted.

The above discussion raises another point: if good “abstract code” has to be produced, the compilation process needs to be careful about the interplay among different inference techniques and to take into account the family of approximations employed. We will see other applications of this general principle.

Since we are about to drop clause 2 from our running example, it is time to unveil a little lie. In fact, for ease of exposition we are describing a simplified compilation process. In China, for instance, quantities which are tied by an equality constraint, such as Y1 = Y2 in clause 2, are merged into only one quantity. This means that, in reality, a quantity corresponds to a set of formulas. This way the set of quantities for clause 2 would be something like {⟨Y1, Y2⟩_{1}, ⟨X2⟩_{2}, ⟨L⟩_{3}, ⟨L1⟩_{4}} and the agents (5.50), (5.51), (5.54), (5.57), and (5.58) would never be generated.

5.10.3 Quantity Arithmetic Agents

A classical quantitative technique is interval arithmetic which allows to infer the variation interval of an expression from the intervals of its sub-expressions. An example inference is the deduction of A · B ∈ (−6, 30) from A ∈ [3, 6) and B ∈ [−1, 5]. Since our ℝapproximation might be something different from straight intervals, we call this inference technique quantity arithmetic.

The abstract compiler examines the quantities of clause 1, in particular

⟨X1⟩_{2}, ⟨Y1⟩_{3}, ⟨B⟩_{5}, ⟨Y1 + B⟩_{9}, ⟨X1 + B⟩_{10},

and produces the agents

X̂_{9}^{q} ⊆ X̂_{3}^{q} ⊞ X̂_{5}^{q}  and  (5.59)
X̂_{10}^{q} ⊆ X̂_{2}^{q} ⊞ X̂_{5}^{q},  (5.60)

on the grounds that

(X1 + B) = (X1) + (B)  and  (5.61)
(Y1 + B) = (Y1) + (B).  (5.62)

But (5.61) can be implicitly manipulated so to obtain

(X1) = (X1 + B) − (B)  and  (5.63)
(B) = (X1 + B) − (X1),  (5.64)

and the same can be done for (5.62). Thus the abstract compiler can emit also the agents

X̂_{3}^{q} ⊆ X̂_{9}^{q} ⊟ X̂_{5}^{q},  (5.65)
X̂_{5}^{q} ⊆ X̂_{9}^{q} ⊟ X̂_{3}^{q},  (5.66)
X̂_{2}^{q} ⊆ X̂_{10}^{q} ⊟ X̂_{5}^{q},  and  (5.67)
X̂_{5}^{q} ⊆ X̂_{10}^{q} ⊟ X̂_{2}^{q}.  (5.68)

Even though in our example only linear arithmetic expressions occur, we can, of course, treat non-linear expressions in a very similar way. The next technique, instead, is limited to linear constraints.

5.10.4 Linear Refinement Agents

Consider clause 1, where the quantities include ⟨Y1⟩_{3} and ⟨B⟩_{5}. The constraint Y1 + B <= 1 can be transformed, by means of trivial algebraic manipulations, into

(Y1) ≤ 1 − (B)  and  (5.69)
(B) ≤ 1 − (Y1),  (5.70)

which can then be approximated by means of the agents^{19}

X̂_{3}^{q} ⊆ (≥) ◫ (A_{ℝ}({1}) ⊟ X̂_{1}^{q})  and  (5.71)
X̂_{1}^{q} ⊆ (≥) ◫ (A_{ℝ}({1}) ⊟ X̂_{3}^{q}).  (5.72)

This simple pattern can be applied to any linear constraint. The agents (5.71) and (5.72) also suggest that it is a good idea to employ ℝ-approximations providing reasonably tight overestimates for the singleton sets containing the numeric constants that can appear in programs.

When using bounded differences a very common form of constraint is subject to a simplified treatment. This form is

                       X1 = X - 1,

and can be found in all sorts of inductive definitions. The agent

X̂_{i_{X},i_{X1}}^{r} ⊆ { (x, y) | x, y ∈ ℝ, x − y = 1 }

achieves, through quantity refinement, the same effect of the linear refinement agents that can be derived from X1 = X - 1. Notice that this applies also to inequality constraints like X1 < X - 1. It also applies in the context of the previous section, when our set of quantities includes, say, the formulæ X+1 and X.

19Reminder: we are assuming that A_{ℝ^{2}} includes ordering relationships.

5.10.5 Relational Arithmetic Agents

Another important technique is relational arithmetic [Sim86] that infers constraints on the qualitative relationship of an expression to its arguments. It can be given by a number of axiom schemata that are strongly dependent on the ℝ^{2}-approximation employed. For instance, when using ordering relationships the following are valid, for each ▷◁ ∈ {=, ≠, ≤ <, ≥, >}:

x ▷◁ 0 ⟺ (x + y) ▷◁ y  (5.73)
x ▷◁ y ⟺ (x + z) ▷◁ (y + z)  (5.74)
x ▷◁ y ⟺ e^{x} ▷◁ e^{y}  (5.75)
(x > 0 ∧ y > 0) ⟹ \{ \begin{matrix}x ▷◁ 1 ⟹ (x ∗ y) ▷◁ y \\ y ▷◁ 1 ⟹ (x ∗ y) ▷◁ x \end{matrix}  (5.76)
(x > 0 ∧ y < 0) ⟹ (x ▷◁ −y ⟹ −1 ▷◁ (x/y))  (5.77)

Notice that this is only a small selection of the many axiom schemata on which relational arithmetic can be based. Observe also that there is no restriction to linear constraints.

If we consider clause 1 of our running example, whose quantities include

⟨X1⟩_{2}, ⟨Y1⟩_{3}, ⟨B⟩_{5}, ⟨Y1 + B⟩_{9}, ⟨X1 + B⟩_{10},

then axioms (5.73) and (5.74) justify the introduction of the following relational arithmetic agents:

X̂_{10,2}^{r} ⊆ X̂_{5}^{q} ⊠ A_{ℝ}({0}),
X̂_{10,5}^{r} ⊆ X̂_{2}^{q} ⊠ A_{ℝ}({0}),
X̂_{9,3}^{r} ⊆ X̂_{5}^{q} ⊠ A_{ℝ}({0}),
X̂_{9,5}^{r} ⊆ X̂_{3}^{q} ⊠ A_{ℝ}({0}),
X̂_{10,9}^{r} ⊆ X̂_{2,3}^{r},
X̂_{2,3}^{r} ⊆ X̂_{10,9}^{r}.

Observe that the axiom schemata (5.73) and (5.74) hold also when the ℝ^{2}-approximation employed consists of bounded differences. Notice also that a reasonable system of bounded differences is likely to satisfy

A ⊠ A_{ℝ}({0}) = d(A),

for each A ∈ A_{ℝ} (see Definition 83 on page 197).

5.11 Binding Agents

Here we deal with the problem of reflecting into an abstract constraint (namely, a collection of agents) the changes that are induced by the unification of two terms. Starting with an ask-and-tell constraint C, we must produce another constraint C′ as specified in Sections 4.5.2 on page 137, for the case where the occur-check is performed at the concrete level, and 4.6.1 on page 152, when the occur-check is omitted. The new agent C′ will be obtained by adding some new agents to C, by closing the result with respect to the inference map, and by projecting away some information.

So we have an unknown n-tuple of multisets of real numbers, X̂, that describes an unknown set of n-tuples of ground terms T̂. Let us arbitrarily choose

(t_{1}, …, t_{n}) ∈ T̂.

All what we know is that t_{h} = u, for some term u. We distinguish the following cases along the lines of Section 4.5.2: for each case the agents to be added to C are specified.

Binding to a Symbolic Constant

A symbolic constant does not contain any numerical leaf. Thus we can add the agent

X̂_{h}^{c} ⊆ {0}.  (5.78)

Binding to a Number

A number u has a single numerical leaf: u itself. The agents

X̂_{h}^{c} ⊆ A_{ℕ}({1})  and  (5.79)
X̂_{h}^{q} ⊆ A_{ℝ}({u})  (5.80)

specify this fact.

Binding to an Alias

Here we know that u = t_{j} for some j ∈ {1, …, n} with j ≠ h. This means that t_{h} and t_{j} are identical, which can be abstracted by

X̂_{h}^{c} ⊆ X̂_{j}^{c},  (5.81)
X̂_{j}^{c} ⊆ X̂_{h}^{c},  (5.82)
X̂_{h}^{q} ⊆ X̂_{j}^{q},  and  (5.83)
X̂_{j}^{q} ⊆ X̂_{h}^{q}.  (5.84)

Relations are clearly inherited, too. Thus we can add the following agents, for each m = {1, …, n} \ {h, j}:

X̂_{hm}^{r} ⊆ X̂_{jm}^{r},  (5.85)
X̂_{jm}^{r} ⊆ X̂_{hm}^{r}.  (5.86)

Binding to a Compound

In this case u is obtained by instantiation from a “skeleton term” s that is a compound term. More precisely, we can assume, without loss of generality, that vars(s) = {Y_{j_{1}}, …, Y_{j_{l}}}, with l ≥ 0 and

{j_{1}, …, j_{l}} ⊂ {1, …, n} \ {h},

and that

u = t_{h} = s[t_{j_{1}}/Y_{j_{1}}, …, t_{j_{l}}/Y_{j_{l}}].  (5.87)

Let us consider the extension of nl^{+} : T → ℘f^{+}(ℝ) to possibly nonground terms: nl^{+} : T_{Vars} → ℘f^{+}(ℝ) is obtained by considering that a variable is not a numerical leaf. Then, Equation (5.87) implies

nl^{+}(t_{h}) = nl^{+}(s) ⊎ ⨄_{i=1}^{l} nl^{+}(t_{j_{i}}).  (5.88)

This implies both

∥nl^{+}(t_{h})∥ = ∥nl^{+}(s)∥ + ∑_{i=1}^{l}∥nl^{+}(t_{j_{i}})∥  (5.89)

and

ζ(nl^{+}(t_{h})) = ζ(nl^{+}(s)) ∪ ⋃_{i=1}^{l} ζ(nl^{+}(t_{j_{i}})).  (5.90)

Now, Equation 5.89 is equivalent, for each k = 1, …, l, to

∥nl^{+}(t_{j_{k}})∥ = ∥nl^{+}(t_{h})∥ − (∥nl^{+}(s)∥ + ∑_{\begin{matrix}i=1 \\ i≠k \end{matrix}}^{l}∥nl^{+}(t_{j_{i}})∥) (5.91)

whereas (5.90) implies, again for each k = 1, …, l,

ζ(nl^{+}(t_{j_{k}}) ⊆ ζ(nl^{+}(t_{h})).  (5.92)

If we stipulate that c_{s} =^{def} ∥nl^{+}(s)∥ and q_{s} =^{def} ζ(nl^{+}(s)), we have justified the addition to C of the following agents:

X̂_{h}^{c} ⊆ A_{ℕ}({c_{s}}) ⊞ ⊞_{i=1}^{l} X̂_{j_{i}}^{c},  (5.93)
X̂_{j_{k}}^{c} ⊆ X̂_{h}^{c} ⊟ (A_{ℕ}(\{c_{s}\}) ⊞ ⊞_{\begin{matrix}i=1 \\ i≠k \end{matrix}}^{l} X̂_{j_{i}}^{c}), for k = 1, …, l, (5.94)

X̂_{h}^{q} ⊆ A_{ℝ}(q_{s}) ⊕ ⨁_{i=1}^{l} X̂_{j_{i}}^{q},  (5.95)
X̂_{j_{k}}^{q} ⊆ X̂_{h}^{q},  for k = 1, …, l. (5.96)

For relations we reason as follows. Equation (5.92) implies that if the numerical leaves of t_{h} are in some relation with the numerical leaves of, say, t_{m}, then this is true also for the subset of numerical leafs that are in t_{j_{k}}. This justifies the agents

X̂_{j_{k}m}^{r} ⊆ X̂_{hm}^{r}  (5.97)

for each k = 1, …, l and each m = 1, …, n with m ≠ j_{k}. On the other hand, the numeric leaves of t_{h} can be obtained by “putting together” the pieces indicated by (5.90). If we join the relations that hold between each piece and the leaves of t_{m} we obtain a relation that holds for the entire collection. We thus add the agents

X̂_{hm}^{r} ⊆ (A_{ℝ}(q_{s}) ⊠ X̂_{m}^{q}) ⊕ ⨁_{i=1}^{l} X̂_{j_{i}m}^{r},  (5.98)

for each m ∈ {1, …, n} \ {h, j_{1}, …, j_{l}}.

Cyclic Binding to a Compound

In this case, the handling of cyclic binding turns out to be easy. However, we need an approximation for subsets of the extended naturals ℕ∪{∞}. This ℕ_{∞}-approximation (whose simple definition is omitted) is needed for cardinalities, since a cyclic term may have an infinite number of numerical leaves.

With this change, cyclic bindings are very similar to normal bindings. They are thus handled by means of the agents described in the previous paragraph, where {j_{1}, …, j_{l}} has been replaced by {j_{1}, …, j_{l}} \ {h}.

5.12 Making It Practical

We have set up a very precise domain for range and relations analysis. Indeed, being obtained from a non-trivial constraint system by means of the ask-and-tell construction, this domain is much too complex to be practical. We attack this complexity from two sides:

1. on one side, all the agents constituted by basic constraints (see Definition 88 on page 207) and all the implicit agents can be efficiently represented and dealt with by means of constraint networks; 2. on the other side, we can apply the golden rule of abstract

interpretation: information can be safely thrown away at any time.

5.12.1 Representing Basic Constraints and Implicit Agents

The term constraint network is very general, and has been used to define a wide variety of structures. Here we define a particular kind of constraint networks that suits our purposes.

Definition 92 (Constraint network.) Let A_{ℕ}, A_{ℝ}, and A_{ℝ^{2}} be approximations for ℕ, ℝ, and ℝ^{2}, respectively. A constraint network is a structure of the form

(n, ℓ, ℓ̇, ℓ̈),

where

1. n ∈ ℕ; the members of Q_{n} =^{def} {1, …, n} are called quantities; an element of R_{n} =^{def} { (i, j) | i, j ∈ Q_{n}, i ≠ j } is called a proper pair of the constraint network;

2. ℓ: Q_{n} → A_{ℕ} assigns a cardinality to each quantity;

3. ℓ̇: Q_{n} → A_{ℝ} assigns a range to each quantity;

4. ℓ̈: R_{n} → A_{ℝ^{2}} assigns a relation to each proper pair.

In practice, a constraint network is a complete, directed, and labelled graph where we have two labels for the nodes and one label for the edges. Each constraint network corresponds to an element of D_{n}^{♯} as given in (5.28) on page 200. Constraint networks are also a compact representation for any set of basic constraints.

A constraint network can be inconsistent.

Definition 93 (Consistency.) Let Q =^{def} (n, ℓ, ℓ̇, ℓ̈) be a constraint network. Q is said inconsistent if at least one of the following conditions holds:

1. there exists i ∈ Q_{n} such that ℓ(i) = ∅;

2. there exists i ∈ Q_{n} such that ℓ(i) ≥ 1 and ℓ̇(i) = ∅;

3. there exist (i, j) ∈ R_{n} such that ℓ̈(i, j) = ∅ and either ℓ(i) ≥ 1 or ℓ(j) ≥ 1.

The constraint network Q is said consistent otherwise.

Another advantage of constraint networks is that all the implicit agents need not be represented explicitly. The kernel operators they denote are simply realized by graph algorithms. In the following presentation of the algorithms we will make use of the notation ℓ̂(o) := e, where ℓ̂ is one of ℓ, ℓ̇, or ℓ̈, o is a node or a pair, depending on ℓ̂, and e is an expressions. The meaning of ℓ̂(o) := e can expressed, using Hoare logic, as follows:

{ℓ̂(o) = K}
ℓ̂(o) := e
{ℓ̂(o) = K ⊗ e}

Transitive Closure

A simple algorithm for applying transitive closure up to quiescence can be obtained by a suitable variation of Warshall/Warren algorithm for graph closure [War62, War75]. A first, naive version of it is given as Algorithm 3.

Require: any constraint network (n, ℓ, ℓ̇, ℓ̈) Ensure: ∀i, j, k ∈ Q_{n} : ℓ(j) ≥ 1 ⟹ ℓ̈(i, k) ⊆ ℓ̈(j, k) ⧇ ℓ̈(i, j)

for all j ∈ Q_{n} do

if ℓ(j) ≥ 1 then

for all i ∈ Q_{n} do for all k ∈ Q_{n} do

ℓ̈(i, k) := ℓ̈(j, k) ⧇ ℓ̈(i, j)

Algorithm 3: Naive algorithm for transitive closure.

Require: any constraint network (n, ℓ, ℓ̇, ℓ̈) Ensure: ∀i, j ∈ Q_{n} : ℓ(i) ≥ 1 ⟹ ℓ̇(j) ⊆ ℓ̈(i, j) ◫ ℓ̇(i) 1: repeat

2: changed := false

3: for all i ∈ Q_{n} do 4: if ℓ(i) ≥ 1 then 5: for all j ∈ Q_{n} such that i ≠ j do 6: ℓ̇(j) := ℓ̈(i, j) ◫ ℓ̇(i) 7: if ℓ̇(j) has changed then

8:  changed := true

9: until changed = false Algorithm 4: Naive algorithm for quantity refinement.

Quantity Refinement

An almost obvious algorithm which applies quantity refinement as much as possible is Algorithm 4 on the preceding page. Perhaps, the only non-obvious part is the need for the outer iteration. To see why this is necessary, let us modify the example above. Suppose that we have three quantities: A ∈ [1 .. 6] and B, C ∈ [4, 9]. Suppose also that we have the relations A > B ≥ C and A ℝ^{2} C. If we apply refinement to B and C or to A and C we cannot deduce anything new. Then we can apply refinement to A and B, obtaining A ∈ [5 .. 6] and B ∈ [4, 6), as we have seen before. But now we must apply refinement to B and C again in order to obtain C ∈ [4, 6).

Indeed, Algorithm 4 is naive under several aspects. First of all it considers also the pairs from which no refinement is possible. This is easily fixed by changing line 5 so to read as

for all j ∈ Q_{n} such that i ≠ j and ℓ̈(i, j) ≠ ℝ^{2} do

What we have obtained is an algorithm called AC-1 [Mac77]. More serious arc-consistency algorithms originate from the Waltz filtering algorithm [Wal75], which was later named AC-2. A simpler and more general version of AC-2, known as AC-3, is given as Algorithm 5. It improves on AC-1 by avoiding repeated refinement using quantities which have not changed.

Numeric Constraint Propagation

The algorithm for numeric constraint propagation given as Algorithm 6 on the following page is self-explanatory.

The remaining agents can be “interpreted” in the standard way (see, e.g., [VSD92a]) thus implementing the associated kernel operators. One thing worth noticing, though it is well-known, is that the functional composition of two kernel operators is not necessarily a kernel operator. Thus, the algorithms for transitive closure, Require: any constraint network (n, ℓ, ℓ̇, ℓ̈) Ensure: ∀i, j ∈ Q_{n} : ℓ(i) ≥ 1 ⟹ ℓ̇(j) ⊆ ℓ̈(i, j) ◫ ℓ̇(i)

S := { (i, j) | i, j ∈ Q_{n}, ℓ(i) ≥ 1, ℓ̈(i, j) ≠ ℝ^{2} } while S ≠ ∅ do

select (h, k) ∈ S S := S \ {(h, k)} ℓ̇(k) := ℓ̈(h, k) ◫ ℓ̇(h) if ℓ̇(k) has changed and ℓ(k) ≥ 1 then S := S ∪ { (k, l) | l ∈ Q_{n}, ℓ̈(k, l) ≠ ℝ^{2} }

Algorithm 5: AC-3 algorithm for quantity refinement.

Require: any constraint network (n, ℓ, ℓ̇, ℓ̈) Ensure: ∀i, j ∈ Q_{n} : i ≠ j ⟹ ℓ̈(i, j) ⊆ ℓ̇(i) ⊠ ℓ̇(j) 1: for all i ∈ Q_{n} do 2: for all j ∈ Q_{n} such that i ≠ j do

3:  ℓ̈(i, j) := ℓ̇(i) ⊠ ℓ̇(j)

Algorithm 6: Algorithm for numeric-constraint-propagation. quantity refinement, numeric constraint propagation, and for the interpretation of all the other agents must be enclosed in an outer loop and iterated, in principle, until quiescence. However, depending on the chosen approximations for cardinalities, sets, and binary relations, quiescence is not guaranteed to happen. This brings us to the next section.

5.12.2 Widenings

Most members of the family of numerical domains we have presented^{20} either have infinite height, or their height can be considered infinite by any practical means. Thus widening operators are undoubtedly required in order to ensure or speed-up the convergence of the analysis. We have widenings in several of the domain components: widenings for the ℕ-, ℝ-, and ℝ^{2}-approximations, serve as a base for designing widening operators at the higher levels.

However, the practicality of the domain relies also on unary widenings: unary operators that allow losing information without losing correctness. They typically consist in throwing away agents. Since our domain is monotonic this is always safe. An example where this is absolutely necessary was announced at the end of the previous section. Suppose we have the set of agents constituted by

X̂_{1}^{q} ⊆ [0, +∞),  (5.99)
X̂_{2}^{q} ⊆ [0, +∞),  (5.100)
X̂_{1}^{q} ⊆ X̂_{2}^{q} ⊞ [1, 1],  (5.101)
X̂_{2}^{q} ⊆ X̂_{1}^{q} ⊞ [1, 1].  (5.102)

They give rise to an infinite (or virtually infinite) sequence of refinements. Even though this kind of situation occurs rarely, a unary widening is needed. In the current version of China the system sets a watchdog timer before entering the “agents interpreter”. In the above situation, a timeout condition occurs and the execution of a unary widening is triggered. This widening will identify the loop constituted by (5.101) and (5.102) in the dependency graph of the interpreter and will arbitrarily kill one or both the guilty agents.

20We have a specific domain for each possible choice of the various parameters.

Unary widenings are also used in other places. In general, they are employed in order to throttle complex agents. One extreme possibility is to remove all the non-basic agents after each meet operation of the ask-and-tell constraint system. This clearly results in a very fast analysis, but all the “global effects” captured by complex agents are lost. Alternatively, heuristics can be used in order to decide which agents to evict. Unary widenings are also applied during the join operation. For example, the first domain of Janssens et al. [JBE94], if recasted in our setting, prescribes the removal of all the non-basic agents before each join operation.

5.13 Conclusion

We have shown that the compile-time detection of redundant numeric constraints in CLP programs can play a crucial role in the field of semantics based program manipulation. This is especially true for the area of optimized compilation, where they can enable major performance leaps. Besides reviewing known optimizations, we have proposed a novel optimization based on call-graph simplification. The nice thing about the simplified call-graph optimization is that it is an optimization in a strict sense: in exchange for a small increase in code size, the optimized program is guaranteed to be at least as fast as the unoptimized one. Moreover, this optimization helps when the known indexing techniques, that are normally used to reduce the search space, fail.

Even though the analysis of numeric constraints has many important application, and despite the fact that almost every existing CLP language incorporates some kind of numeric domain, this research topic is relatively unexplored. We have described a sequence of approximations for characterizing, by means of ranges and relations, the values of the numerical leaves that appear in Herbrand terms. In particular, we have illustrated for the first time the need for cardinality information. For each approximation needed for the analysis, we have presented different alternatives that allow for dealing with the complexity/precision tradeoff in several ways.

We have then presented a sophisticated numeric domain, which is obtained (among other things) by means of the ask-and-tell construction of Chapter 3. The ask-and-tell constraint system constitutes a very convenient formalism for expressing both

• efficient reasoning techniques originating from the world of artificial intelligence, where approximate deduction holds the spotlight since the origins; and

• the abstract operations of the domain.

In practice, we have defined a family of concurrent languages that serve as target-languages in an abstract compilation approach. One notable advantage is that the correctness proof is constructive: every abstract agent has been introduced at the end of an argument that shows its correctness.

The future work is of experimental nature. We have developed, in the context of the China analyzer, a modular implementation of the numeric domain presented in this chapter. Modularity is essential, due to the many degrees of freedom that characterize the construction. Up to now, the emphasis of the development has been on correctness, but keeping into account that efficiency must finally be obtained. We have just started working on the optimization of the domain’s components. Then we will turn to the experimentation of several widening strategies: something that, today, is a completely empirical activity.

236  Chapter 5. Range and Relations Analysis

Definiteness Analysis

\underline{Contents}

6.1 Introduction . . . . . . . . . . . . . . . . . 237 6.2 Boolean Functions for Groundness Anal-

ysis . . . . . . . . . . . . . . . . . . . . . . . 240 6.3 Combination of Domains and Reactivity 242 6.4 Binary Decision Trees and Diagrams … 246 6.5 Is x Ground? . . . . . . . . . . . . . . . . . 249 6.6 Extracting Sure Groundness from ROB-

DDs . . . . . . . . . . . . . . . . . . . . . . 250 6.7 A New, Hybrid Implementation for Pos 255 6.8 Experimental Evaluation . . . . . . . . . . 262 6.9 Conclusion . . . . . . . . . . . . . . . . . . 266

6.1 Introduction

The task of groundness analysis (or definiteness analysis as it is also referred to) is to derive, for all the program points of interest, whether a certain variable is bound to a unique value (or ground). This kind of information is very important: it allows substantial optimizations to be performed at compile-time, and is also crucial for most semantics-based program manipulation tools. Moreover, many other analyses are made much more precise by the availability of groundness information. For these reasons, the subject of groundness analysis for (constraint) logic programs has been widely studied. After the early attempts, some classes of Boolean functions have been recognized as constituting good abstract domains for groundness analysis [CFW91, CH93, AMSS, HCC95]. In particular, the set of positive Boolean functions, (namely, those functions that assume the true value under the valuation assigning true to all variables), which is denoted by Pos, allows to express Boolean properties of program variables where the property of one variable may depend on that property of other variables. For groundness analysis, since variables can be bound to terms containing other variables, the groundness of one variable may depend on the groundness of other variables. Pos has been recognized as the most precise domain for capturing the kind of dependencies arising in groundness analysis.

This ability to express dependencies makes analysis based on Pos very precise, but also makes it relatively expensive, as many operations on Boolean formulas have exponential worst case complexity. Armstrong et al. [AMSS94a] analyzed many representations of positive Boolean formulas for abstract interpretation, and found Reduced Ordered Binary Decision Diagrams (ROBDDs) to give the best performance.

ROBDDs [Bry86], also known as Bryant graphs, are a representation for Boolean functions supporting many efficient operations. Because of this, they have often been used to implement Pos for abstract interpretation. Indeed, ROBDDs are general enough to represent all Boolean functions. However, nobody has yet succeeded in exploiting the (seemingly very small) peculiarities of positive functions in order to obtain a more efficient implementation.

Several authors have reported successful experiences using ROB-DDs for groundness analysis (see, e.g., [CH93, AMSS]). However, in the literature there is no reference to the problem of detecting, as efficiently as possible, those variables which are deemed to be ground in the context of a ROBDD. This is not surprising, since most currently implemented analyzers need to derive this information only at the end of the analysis and only for presentation purposes. In these cases efficiency is not a problem and the simple approaches are sufficient. Things are very different when this information is required during the analysis. This need arises when dealing with languages which employ some sort of delay mechanism, which is typically based on groundness conditions. One of these languages is CLP(R) [JMSY92b], where non-linear constraints are delayed until they become linear; only then are they sent to the constraint solver. In the context of our work on data-flow analysis for CLP(R) we thus faced the following problem: in programs with many non-linear constraints, the abstract interpreter spends a lot of time deciding whether a constraint is delayed or not. In the early implementations of the China analyzer this kind of information (which is needed quite often) was derived using the ROBDD package itself (see Section 6.5). This had the advantage of making possible the use of untouched, readily-available ROBDD software, while having the big disadvantage of inefficiency.

In this chapter we introduce and study the problem of quick detection of ground variables using ROBDDs. We first propose an easy, even though not completely satisfactory, solution. We then take a different approach where we represent Pos functions in a hybrid way: ground variables are represented explicitly, while ROBDDs come into play only for dependency and disjunctive information. This solution uses the more efficient representation for each kind of information: “surely ground variables” are best represented by means of sets (bit-vectors, at the implementation level), whereas ROBDDs are used only for “conditional” and “disjunctive” information. In such a way, besides making the information about ground variables readily available, we can keep the ROBDDs generated during the analysis as small as possible. This promises to be a win, given that most real programs (together with their typical callpatterns) exhibit a high percentage of variables which are ground at the program points of interest. Notice that Boolean functions are used in the more general context of dependency analysis, including finiteness analysis for deductive database languages [BDM92] and suspension analysis for concurrent (constraint) logic programming languages [FGMP95]. The techniques we propose might be useful also in these contexts. However, this is something we have not studied yet. In Section 6.2 we briefly review the usage of Boolean functions for groundness analysis of (constraint) logic programs (even though we assume familiarity on this subject). Section 6.3 presents the main motivations of this work. Binary-decision trees and diagrams, and the problem of extracting sure groundness information from them are introduced in sections 6.4 and 6.5. In Section 6.6 we show a first non-trivial solution to the problem, while Section 6.7 introduces the hybrid domain. The results of the experimental evaluation are reported in Section 6.8. Section 6.9 concludes with some final remarks.

6.2 Boolean Functions for Groundness Analysis

After the early approaches to groundness analysis [Mel85, JS87], which suffered from serious precision drawbacks, the use Boolean functions has become customary in the field. The reason is that Boolean functions allow to capture in a very precise way the groundness dependencies which are implicit in unification constraints such as z = f(g(x), y): the corresponding Boolean function is (x∧y) ↔ z, meaning that z is ground if and only if x and y are so. They also capture dependencies arising from other constraint domains: for instance, x+2y+z = 4 can be abstracted as ((x∧y) → z)∧((x∧z) → y) ∧ ((y ∧ z) → x). We now introduce Boolean valuations and functions in a way which is suitable for what follows. Vars is a fixed denumerable set of variable symbols.

Definition 94 (Boolean valuations.) The set of Boolean valuations over Vars is given by A =^{def} Vars → {0, 1}. For each a ∈ A, each x ∈ Vars, and each c ∈ {0, 1} the valuation a[c/x] ∈ A is given, for each y ∈ Vars, by

a[c/x](y) =^{def} \{ \begin{matrix}c, & if x = y; \\ a(y), & otherwise. \end{matrix}

For X = {x_{1}, x_{2}, … } ⊆ Vars, we write a[c/X] for a[c/x_{1}][c/x_{2}] · · ·.

Definition 95 (Boolean functions.) The set of Boolean function over Vars is F =^{def} A → {0, 1}. The distinguished elements ⊤, ⊥ ∈ F are the functions defined by ⊤ =^{def} λa ∈ A . 1 and ⊥ =^{def} λa ∈ A . 0. For f ∈ F, x ∈ Vars, and c ∈ {0, 1}, the function f[c/x] ∈ F is given, for each a ∈ A, by f[c/x](a) =^{def} f(a[c/x]). When X ⊆ Vars, f[c/X] is defined in the obvious way.

The question whether a Boolean function f forces a particular variable x to be true (which is what, in the context of groundness analysis, we call sure groundness information) is equivalent to the question whether f → x is a tautology (namely, f → x = ⊤). In what follows we will also need the notion of dependent variables of a function.

Definition 96 (Dependent and true variables.) For f ∈ F, the set of variables on which f depends and the set of variables necessarily true for f are given, respectively, by

vars(f) =^{def} { x ∈ Vars | ∃a ∈ A . f(a[0/x]) ≠ f(a[1/x]) },
true(f) =^{def} { x ∈ vars(f) | ∀a ∈ A : f(a) = 1 ⟹ a(x) = 1 }.

Two classes of Boolean functions which are suitable for groundness analysis are known under the names of Def and Pos (see [AMSS] for details). Pos consists precisely of those functions assuming the true value under the everything-is-true assignment (i.e., f ∈ Pos if and only if f ∈ F and f[1/Vars] = ⊤). Pos is strictly more precise than Def for groundness analysis [AMSS]. The reason is that the elements of Pos allow to maintain disjunctive information which is, instead, lost in Def.

6.3 Combination of Domains and Reactivity

It is well-known that different data-flow analyses can be combined together. In the framework of abstract interpretation this can be achieved by means of standard constructions such as reduced product and down-set completion [CC79, CC92a]. The key point is that the combined analysis can be more precise than each of the component ones for they can mutually improve each other. However, the degree of cross-fertilization is highly dependent on the degree and quality of interaction taking place among the component domains. For the limited purpose of this discussion, when we talk about combination of domains we refer to the following situation: we have several distinct (both conceptually and at the implementation level) analysis’ domains and, for the sake of ensuring correctness or improving precision, there must be a flow of information between them. This can be formalized in different ways. A methodology for the combination of abstract domains has been proposed in [CLV94]. It is based onto low level actions such as tests and queries. Basically, the component domains have the ability of querying other domains for some kind of information. Of course, they must also be able to respond to queries from other domains. For instance, the operations of a domain for numerical information might ask a domain for groundness whether a certain variable is guaranteed to be ground or not. Another way of describing this kind of interaction is the one proposed in [Bag97]. Here the interaction among domains is asynchronous in that it can occur at any time, or, in other words, it is not synchronized with the domain’s operations. This is achieved by considering so called ask-and-tell constraint systems built over product constraint systems. These constraint systems allow to express communication among domains in a very simple way. They also inherit all the semantic elegance of concurrent constraint programming languages [Sar93], which provide the basis for their construction. We will now see, staying on an intuitive level and following the approach of [Bag97] for simplicity, examples of how these combinations look like.

In the CLP(R) system [JMSY92b] non-linear constraints (like X = Y ∗ Z) are delayed (i.e., not treated by the constraint solver) until they become linear (e.g., until either Y or Z are constrained to take a single value). Obviously, this cannot be forgotten in abstract constraint systems intended to formalize correct data-flow analyses of CLP(R). When the abstract constraint system is able to extract numerical information from non-linear constraints (such as the one proposed in [BGL93]), the abstraction α_{N}(X = Y ∗ Z) cannot be used without considering the groundness of Y and Z. By doing this you would incur the risk of overshooting the concrete constraint system (thus loosing soundness), which is unable to deduce anything from non-linear constraints. The right thing to do is to combine the numeric abstract constraint system with one for groundness and using, instead of the abstraction α_{N}(X = Y ∗ Z), the agent

A =^{def} ask(ground(Y); ground(Z)) → α_{N}(X = Y ∗ Z). (6.1)

The intuitive reading is that the abstract constraint system is not allowed to do anything with X = Y ∗ Z until Y or (this is the intuitive reading of the semicolon) Z are ground. In this way, all the abstractions of non-linear constraints are “disabled” until their wake-up conditions are met (in the abstract, which, given a sound groundness analysis, implies that these conditions are met also at the concrete level). The need for interaction between groundness and numerical domains does not end here. Consider again the constraint X = Y ∗ Z: clearly X is definite if Y and Z are so. But we cannot conclude that the groundness of Y follows from the one of X and Z, as we need also the condition Z ≠ 0. Similarly, we would like to conclude that X is definite if Y or Z have a zero value. Thus we need approximations of the concrete values of variables (i.e., bounds analysis), something which is not captured by common groundness analyses while being crucial when dealing with non-linear constraints. In the approach of [Bag97] X = Y ∗Z would be abstractly compiled into an agent of the form^{1}

A ∥ ask(ground(Y) ∧ ground(Z)) → tell(ground(X))
∥ ask(Y = 0; Z = 0) → tell(ground(X))

∥ ask(ground(X) ∧ ground(Z) ∧ Z ≠ 0) → tell(ground(Y)) ∥ ask(ground(X) ∧ ground(Y) ∧ Y ≠ 0) → tell(ground(Z)),

where A is the agent given in Equation (6.1). Of course, this is much more precise than the Pos formula X ← Y ∧ Z, which is all you can say about the groundness dependencies of X = Y ∗ Z if you do not have any numerical information. It is clear from these examples that, when analyzing CLP(R) programs there is a bidirectional flow of information: groundness information is required for a correct handling of delayed constraints and thus for deriving more precise numerical patterns which, in turn, are used to provide more precise groundness information. Indeed, we are requiring a quite complicated interaction between domains.

1We choose this form of presentation for clarity. It is clear that this agent will be itself compiled to something different. For instance, the second agent of the parallel composition will “live” in the groundness component, if the latter is able to capture the indicated dependency.

Another application of groundness analysis with fast access to ground variables is for aliasing analysis. The most popular domain for this kind of analysis is Sharing [JL89]. Without going into details, its strength over the previous approaches [JS87, Deb89] comes from the fact that it keeps track of groundness dependencies. In fact, Sharing has, as far as groundness information is concerned, the same power of Def. When Pos is used for groundness, using Sharing for aliasing at the same time is a waste: Sharing spends time and space for keeping track of groundness, which is already done, and more precisely, by Pos. A possible solution is to adopt a variation of the domains proposed in [JS87, Deb89] (which are much less computationally expensive than Sharing) and to combine it with Pos. We are currently working along this line. This, however, is beyond the scope of this work.

Whatever conceptual methodology you follow to realize the combination of any domain with one for groundness, a key component for the efficiency is that the implementation of the latter must be reactive. By this we mean that: (a) it must react quickly to external queries about the groundness of variables; and, (b) it must absorb quickly groundness notifications coming from other domains.

(a) x [figure] y [figure] z [figure] 1[figure] [figure]0 [figure] [figure] ⋆ z [figure] 0[figure] [figure]1 [figure] [figure] z ⋆ [figure] 0[figure] [figure]1  (b) x [figure] y [figure] z [figure] 1[figure] [figure]0 [figure] [figure]0 [figure] [figure] y [figure] z [figure] 0[figure] [figure]1 [figure] [figure]0

Figure 6.1: OBDTs for (x ∧ y) ↔ z (a) and (x ↔ z) ∧ y (b).

6.4 Binary Decision Trees and Diagrams

Binary decision trees (BDTs) and diagrams (BDDs) are well-known representations of Boolean functions [Bry86, Bry92]. Binary decision trees, such as the ones presented in Fig. 6.1 are binary trees where non-terminal nodes are labeled with variable names, while terminal nodes are labeled with the Boolean constants 0 or 1. The value of the represented function, for a given assignment of Boolean values to variables, can be recovered by following a particular path from the root: at any non-terminal node labeled with a variable v, the thick branch is taken if v is assigned to 1, otherwise the thin branch is taken. The terminal node reached by this walk on the tree is the function value. For a non-terminal node n, we will call the node connected to n by means of the thick (resp. thin) edge the true (resp. false) successor of n. A BDD is a directed acyclic graph which can be thought of as obtained from a BDT by collapsing identical subtrees. With reference to Fig. 6.1 (a), the subtrees marked with ‘⋆’ can be collapsed, as well as all the terminal nodes having the same label. The action of collapsing identical subtrees does not change the represented function. Given a total ordering on the variable symbols, an ordered binary decision tree (OBDT) is a BDT where the sequence of variables (associated to non-terminals) encountered in any path from the root is strictly increasing. The trees depicted in Fig. 6.1 are indeed OBDTs where the ordering is such that x ≺ y ≺ z. Applying the very same restriction to BDDs we obtain the notion of ordered binary decision diagram, or OBDD.

Definition 97 (BDTs and OBDTs.) A binary decision tree is any string generated by the grammar

BDT ::= 0 | 1 | ite(v, BDT, BDT)

where v ∈ Vars. The set of all BDTs is denoted by B. The semantics of BDTs is expressed by the function [[·]]: B → F, defined as follows:

[[0]] =^{def} ⊥,  [[1]] =^{def} ⊤,  [[ite(v, b_{1}, b_{0})]] =^{def} ite(v, [[b_{1}]], [[b_{0}]]),

where for each w ∈ Vars, f_{1}, f_{0} ∈ F, and each a ∈ A,

ite(w, f_{1}, f_{0})(a) =^{def} \{ \begin{matrix}f_{1}(a), & if a(w) = 1; \\ f_{0}(a), & if a(w) = 0. \end{matrix}

The subset B_{o} ⊆ B of ordered BDTs (OBDTs) is defined by the following recurrent equation:

B_{o} =^{def} {0, 1} ∪ { ite(v, b_{1}, b_{0}) | v ∈ Vars, b_{1}, b_{0} ∈ {0, 1} }

∪ \{ ite(v, b_{1}, b_{0}) | \begin{matrix}∀i = 0, 1 : b_{i} ∈ B_{o} ∧ \\ ∃w ∈ Vars . ∃b′_{1}, b′_{0} ∈ B_{o} . \\ b_{i} = ite(w, b′_{1}, b′_{0}) ⇒ v ≺ w \end{matrix} \}.

We will deliberately confuse a BDT with the Boolean function it represents. In particular, for b ∈ B, when we write vars(b) or true(b) what we really mean is vars([[b]]) or true([[b]]). This convention of referring to the semantics simplifies the presentation and should not cause problems.

A reduced ordered binary decision diagram, or ROBDD, is an OBDD such that:

1. there are no duplicate terminal nodes;

2. there are no duplicate non-terminal nodes (i.e., nodes having the same label and the same true and false successors);

3. there are no redundant tests, that is each non-terminal node has distinct true and false successors.

Any OBDD can be converted into a ROBDD by repeatedly applying the reduction rules corresponding to the above properties: collapsing all the duplicate nodes into one and removing all the redundant tests, redirecting edges in the obvious way. Application of these rules does not change the represented functions. ROBDDs have one very important property: they are canonical. This means that, for each fixed variables’ ordering, two ROBDDs represent the same function if and only if they are identical.

The nice computational features of ROBDDs make them suitable for implementing Pos (see, e.g., [CH93, AMSS]), even though ROBDDs are clearly able to represent any Boolean function. Here we deal formally only with OBDTs, since our results do not need all the properties of ROBDDs. Indeed, since every OBDT is an OBDD and the reduction rules do not change the represented Boolean function, everything we say about OBDTs is true also for ROBDDs.

6.5. Is x Ground?  249

6.5 Is x Ground?

Capturing dependency and disjunctive information is essential for precise groundness analysis. However, this kind of information is only needed for maintaining precise intermediate results during the analysis. Instead, the only information that is relevant for the user of the analysis’ results is whether a certain variable is guaranteed to be ground at a certain point or not. When combinations of domains are considered, as explained in Section 6.3, it is vital to recover the set of ground variables quickly even during the analysis itself. The problem of deriving this sure groundness information from ROBDDS has not been tackled in previous works [CFW91, CH93, AMSS]. Basically we know about five ways of doing that:

1. Given x ∈ Vars and a ROBDD representation b of a Boolean function f, use the ROBDD package to test whether f → x is a tautology, that is, whether f → x is equivalent to ⊤. This test can be performed in O(|b|) time. The main advantage of this solution is that it does not require any change to the ROBDD package. One of the drawbacks is that the reduction of f → x causes the creation and disposal of “spurious” nodes which must be dealt with by the system^{2}.

2. Given x ∈ Vars and a ROBDD representation b of a Boolean function f, the information whether x is forced to 1 by f is obtained by visiting b. The answer is affirmative if (a) there is at least one node in b labeled with x; and, (b) each node in b labeled with x has its false branch equal to 0. This method has still linear complexity, requires the incorporation of the visit into the ROBDD package, and it is read-only in that is does not involve the creation of any node.

2This is due to technical details which are vital for efficient and realistic implementations. This is, however, beyond the scope of this work.

3. Another possibility is to visit the ROBDD representation b to derive, in one step, the set G of all the variables which are forced to 1. We will see how this can be done in Section 6.6.

4. A variation of the previous method consists in avoiding visiting b, while obtaining exactly the same information, by modifying ROBDD’s nodes so that every node records the set of variables which are forced to true by the Boolean function it represents. Section 6.6 explains how this method of keeping explicit the information about true variables can be easily implemented.

5. The last method is based on a quite radical, though very simple, solution. Intuitively, it is based on the idea of keeping the information about true variables totally separate from dependency and disjunctive information. True variables are represented naturally by means of sets whereas only the dependency and disjunctive information is maintained by means of ROBDDs. This will be explained in Section 6.7.

6.6 Extracting Sure Groundness from ROB-DDs

Here is the only property of OBDTs (and thus of ROBDDs) we need.

Definition 98 (Weak normal form.) A BDT b ∈ B is said to be in weak normal form if and only if either b = 0 or b = 1, or there exist b_{1}, b_{0} ∈ B such that b = ite(v, b_{1}, b_{0}), v ∉ vars(b_{1}) ∪ vars(b_{0}), and both b_{1} and b_{0} are in weak normal form.

Proposition 99 Each OBDT b ∈ B_{o} is in weak normal form.

Proof By induction on the structure of b: the base cases b = 0 and b = 1 are trivial. If b = ite(v, b_{1}, b_{0}) then b_{1} and b_{0} are members of B_{o}, and thus are in weak normal form by the induction hypothesis. Moreover, by the definition of B_{o}, v does not occur neither in b_{1} nor in b_{0}. But then the definition of [[·]] ensures that, for each a ∈ A, [[b_{1}]][0/v] = [[b_{1}]][1/v] and likewise for b_{0}. The thesis now follows. □

This is indeed the distinctive property of “free BDDs”, also called “1-time branching programs”, where no ordering is required but each path from the root is allowed to test a variable only once [Bry92].

Theorem 100 Let b = ite(v, b_{1}, b_{0}) be in weak normal form. Then we have

true(b) = \{ \begin{matrix}true(b_{0}), & if b_{1} = 0; \\ \{v\} ∪ true(b_{1}), & if b_{1} ≠ 0 and b_{0} = 0; \\ true(b_{1}) ∩ true(b_{0}), & otherwise. \end{matrix}

Lemma 101 Let b = ite(v, b_{1}, b_{0}) in weak normal form. Then vars(b) = V ∪ vars(b_{1}) ∪ vars(b_{0}), where

V =^{def} \{ \begin{matrix}\{v\} & if b_{1} ≠ b_{0}, \\ ∅ & otherwise. \end{matrix}

Proof Let x ∈ vars(b), namely, there exists a ∈ A such that b(a[0/x]) ≠ b(a[1/x]). If x = v then b_{1} ≠ b_{0} and x ∈ V. Otherwise, there are still two cases: either a(v) = 1 or a(v) = 0. In the first case, b(a[0/x]) = b_{1}(a[0/x]) and b(a[1/x]) = b_{1}(a[1/x]), thus x ∈ vars(b_{1}). The second case is handled symmetrically, yielding the conclusion x ∈ vars(b_{0}). We have thus shown that vars(b) ⊆ V ∪ vars(b_{1}) ∪ vars(b_{0}).

We now prove that the reverse inclusion also holds. Let us consider x ∈ vars(b_{1}). Since b is in weak normal form, it must be x ≠ v.

Take a ∈ A such that b_{1}(a[0/x]) ≠ b_{1}(a[1/x]), and let b′ = b[1/w]. Then, clearly, b′(a[0/x]) ≠ b′(a[1/x]) and x ∈ vars(b). Symmetrically, taking x ∈ vars(b_{0}) we obtain again that x ∈ vars(b). If we suppose that x ∈ V ∪vars(b_{1})∪vars(b_{0}) but x ∉ vars(b_{1})∪vars(b_{0}), then we have x = v and b_{1} ≠ b_{0}. So, there exists a ∈ A such that b_{1}(a) ≠ b_{0}(a). But then b(a[1/x]) = b_{1}(a[1/x]) = b_{1}(a) and b(a[0/x]) = b_{0}(a[0/x]) = b_{0}(a), thus b(a[0/x]) ≠ b(a[1/x]) and x ∈ vars(b). □

PROOF of Theorem 100. First of all, let us restate the claim in an equivalent, though more convenient way. Let b = ite(v, b_{1}, b_{0}) be in weak normal form. The theorem says that

true(b) = V ∪ V_{1} ∪ V_{0} ∪ V_{10},

where

V =^{def} \{ \begin{matrix}\{V \} & if b_{1} ≠ 0 and b_{0} = 0, \\ ∅ & otherwise; \end{matrix}
V_{1} =^{def} \{ \begin{matrix}true(b_{1}) & if b_{0} = 0, \\ ∅ & otherwise; \end{matrix}
V_{0} =^{def} \{ \begin{matrix}true(b_{0}) & if b_{1} = 0, \\ ∅ & otherwise; \end{matrix}
V_{10} =^{def} true(b_{1}) ∩ true(b_{0}).

The following table shows that V, V_{1}, V_{0}, and V_{10} are pair-wise disjoint.

b_{1} ∈  b_{0} ∈ V = V_{1} =  V_{0} =  V_{10} =
{0}  {0}  ∅  ∅  ∅  ∅
B \ {0}  {0} {v} true(b_{1})  ∅  ∅
{0} B \ {0}  ∅  ∅  true(b_{0})  ∅
B \ {0} B \ {0}  ∅  ∅  ∅ true(b_{1}) ∩ true(b_{0})

In particular, V and V_{1} are disjoint due to the fact that b is in weak normal form. We now show that V ∪ V_{1} ∪ V_{0} ∪ V_{10} ⊆ true(b). If we suppose that x ∈ V ∪ V_{1} ∪ V_{0} ∪ V_{10}, the following cases are mutually exclusive, and all imply that x ∈ true(b).

x ∈ V: this means x = v, b_{1} ≠ 0, and b_{0} = 0. Since b_{1} ≠ b_{0} we have, by Lemma 101, x ∈ vars(b). Also, if a ∈ A is such that b(a) = 1 it must be a(x) = 1.

x ∈ V_{1}: so b_{1} ≠ 0 and b_{0} = 0. From Lemma 101 we have that x ∈ true(b_{1}) ⊆ vars(b_{1}) ⊆ vars(b). Now, if a ∈ A is such that b(a) = 1, it must be b_{1}(a) = 1, but, since x ∈ V_{1} = true(b_{1}), this implies b(x) = 1.

x ∈ V_{0}: this case is symmetric to the previous one.

x ∈ V_{10}: that is x ∈ true(b_{1}) ∩ true(b_{0}), b_{1} ≠ 0, b_{0} ≠ 0, and x ≠ v. In particular, x ∈ true(b_{1}) ⊆ vars(b_{1}) ⊆ vars(b). Let a ∈ A such that b(a) = 1. If a(v) = 1, then b(a) = b_{1}(a) = 1 and thus a(x) = 1, since x ∈ true(b_{1}). If, instead, a(v) = 0, then b(a) = b_{0}(a) = 1 and so a(x) = 1, since x ∈ true(b_{0}).

It remains to be shown that true(b) ⊆ V ∪ V_{1} ∪ V_{0} ∪ V_{10}. We assume that x ∈ true(b), namely that x ∈ vars(b) and

∀a ∈ A : (b(a) = 1 ⟹ a(x) = 1).

Observe that the existence of such an x ensures that b_{1} and b_{0} cannot be both equal to 0. We distinguish the following cases:

x = v: this means b_{1} ≠ 0 and b_{0} = 0, that is x ∈ V.

x ≠ v: this case is further split as follows: b_{1} = 0: for a ∈ A,

b_{0}(a) = 1 ⟹ b(a[0/v]) = 1
⟹ a[0/w](x) = 1
⟹ a(x) = 1,

and thus, ∀a ∈ A : b_{0}(a) = 1 ⇒ a(x) = 1. By Lemma 101 x ∈ {v}∪vars(b_{1})∪vars(b_{0}). Since x ≠ v and b_{1} = 0, this implies x ∈ vars(b_{0}). In conclusion, x ∈ true(b_{0}) = V_{0}.

b_{0} = 0: handled symmetrically. b_{1} ≠ 0 and b_{0} ≠ 0: observe that, for each a ∈ A, b(a) = 1 is

equivalent to

(a(v) = 1 ∧ b_{1}(a) = 1) ∨ (a(v) = 0 ∧ b_{0}(a) = 1).

Suppose, towards a contradiction, that

∃a ∈ A . b_{1}(a) = 1 ∧ a(x) = 0.

We would have b(a[1/v]) = 1 and a[1/v](x) = 0 which is in contrast with the assumption x ∈ true(b). Thus,

∀a ∈ A : (b_{1}(a) = 1 ⟹ a(x) = 1).

Now suppose, ad absurdum, that x ∉ vars(b_{1}), namely

∀a ∈ A : b_{1}(a[1/x]) = b_{1}(a[0/x]).

Let a ∈ A such that b(a) = 1 and b(v) = 1 (such an a must exist, since b_{1} ≠ 0). Now,

b(a) = b_{1}(a) = b_{1}(a[0/x]) = b(a[0/x]) = 1,

but, clearly, a[0/x](x) = 0 which contradicts the assumption x ∈ true(b). We can thus conclude that x ∈ vars(b_{1}) and, by the previous reasoning, that x ∈ true(b_{1}). A symmetric argument proves that also x ∈ true(b_{0}) and thus x ∈ V_{10}. □

This theorem gives us at least two ways of deriving sure groundness information from ROBDDs. One is by implementing a bottomup visit, collecting true variables as indicated. Another one, which is more in the spirit of a reactive implementation, is based on a modification of the node structure which is used to represent ROB-DDs. In standard implementations, a non-terminal node n has one field n.V which holds the test variable, plus two fields n.T and n.F which are references to the nodes which are the roots of the true and false branch, respectively. All the nodes are created by means of a function create(v, @n_{1}, @n_{0}), taking a variable symbol and two references to (already created) nodes, and returning a reference to the newly created node. We can modify this state of things by adding to the node structure a field n.G, containing the set of true variables for the function represented by the ROBDD rooted at n, and by modifying the creation function to initialize n.G as indicated by Theorem 100.

6.7 A New, Hybrid Implementation for Pos

The observation of many constraint logic programs shows that the percentage of variables which are found to be ground during the analysis, for typical invocations, is as high as 80%. This suggests that representing Pos elements simply by means of ROBDDs, as in [CH93, AMSS], is probably not the best thing we can do. Here we propose a hybrid implementation where each Pos element is represented by a pair: the first component is the set of true variables (just as in the domain used in early groundness analyzers [Mel85, JS87]); the second component is a ROBDD. In each element of this new representation there is no redundancy: the ROBDD component does not contain any information about true variables. In fact, as we will see, the hybrid representation has the property that ROBDDs are used only in what they are good for: keeping track of dependencies and disjunctive information. True variables, instead are more efficiently represented by means of sets. The hybrid representation has two major advantages: (a) it is reactive in the sense of Section 6.3; and, (b) it allows for keeping the ROBDDs small, during the analysis, when many variables come out to be true, as it is often the case. Consider Fig. 6.1 (b): the information about y being a true variable (besides not being readily available) requires two nodes. In more involved cases, the information about trueness of a variable coming late in the ordering can be scattered over a large number of nodes. Notice that, while having many true variables, in a straight ROBDD implementation, means that the final ROBDDs will be very similar to a linear chain of nodes, the intermediate steps still require the creation (and disposal) of complex (and costly) ROB-DDs. This phenomenon is avoided as much as possible in the hybrid implementation. (By ℘_{f}(Vars) we denote the set of all finite subsets of Vars.)

Definition 102 (Hybrid representation.) The hybrid representation for Pos is

G =^{def} { ⟨G, b⟩ | G ∈ ℘_{f}(Vars), b ∈ B_{o}, vars(b) ∩ G = true(b) = ∅ }.

The meaning of G’s elements is given by the overloading [[·]]: G → F:

[⟨G, b⟩] =^{def} ⋀(G) ∧ [[b]],

where ⋀{x_{1}, …, x_{n}} =^{def} x_{1} ∧ ⋯ ∧ x_{n} and ⋀ ∅ =^{def} ⊤.

Now, we briefly review the operations we need over Pos (and thus over G) for the purpose of groundness analysis. The constraint accumulation process requires computing the logical conjunction of two functions, the merge over different computation paths amounts to logical disjunction, whereas projection onto a designated set of variables is handled through existential quantification. Functions of the kind x ↔ (y_{1}, …, y_{m}), for m ≥ 0, accommodate both abstract mgus and the combination operation in domains like Pat(Pos) [CLV94].

B_{o} op Complexity Meaning  G op
b_{1} ¨∧ b_{2} O(|b_{1}||b_{2}|) [[b_{1}]] ∧ [[b_{2}]] g_{1} ⊗ g_{2}
b_{1} ¨∨ b_{2} O(|b_{1}||b_{2}|) [[b_{1}]] ∨ [[b_{2}]] g_{1} ⊕ g_{2}
¨∃\overline{V} b  O(|b|^{2^{|V |}})  ∃\overline{V} [[b]]  ∃∃\overline{V} g  Note: V ⊆ vars(b).
¨⋀(V)  O(|V |)  ⋀(V)  Note: V ≠ ∅.
x ¨↔ V  O(|V |)  x ↔ ⋀(V)  Note: x ∉ V.
b[1/V]  O(|b|)  [[b]][1/V]

Table 6.1: Operations defined over B_{o} and G.

Before introducing the G’s operations we introduce, by means of Table 6.1, the needed operations over OBDTs and ROBDDs, their complexity and semantics, as well as the correspondent operations over G. In what follows we will refer to some operations on OBDTs whose meaning and complexity is specified in the table. The restriction operation b[1/V] (also called valuation or co-factoring) is used for maintaining the invariant specified in Definition 102. In the definition of the abstract operators used in groundness analysis, the functions of the form x ↔ (y_{1}, …, y_{m}) are always conjuncted with some other function. For this reason we provide a family of specialized operations (x, V) ⊗^{↔}: G → G, indexed over variables and finite sets of variables. The operation (x, V) ⊗^{↔} builds a representation for (x ↔ ⋀(V)) ∧ f, given one for f.

Definition 103 (Operations over G.) The operation ⊗: G×G →

258  Chapter 6. Definiteness Analysis

G is defined, for each ⟨G_{1}, b_{1}⟩, ⟨G_{2}, b_{2}⟩ ∈ G, as follows:

⟨G_{1}, b_{1}⟩ ⊗ ⟨G_{2}, b_{2}⟩

=^{def} η(G_{1} ∪ G_{2}, b_{1}[1/(G_{2} \ G_{1})] ¨∧ b_{2}[1/(G_{1} \ G_{2})]),

where, for each G ∈ ℘_{f}(Vars) and b ∈ B_{o} such that G∩vars(b) = ∅,

η(G, b)

=^{def} \{ \begin{matrix}⟨G, b⟩, & if true(b) = ∅; \\ η(G ∪ true(b), b[1/ true(b)]), & otherwise. \end{matrix}  (6.2)

The join operation ⊕: G × G → G is given by

⟨G_{1}, b_{1}⟩ ⊕ ⟨G_{2}, b_{2}⟩

=^{def} ⟨G_{1} ∩ G_{2}, (b_{1} ¨∧ ¨⋀(G_{1} \ G_{2})) ¨∨ (b_{2} ¨∧ ¨⋀(G_{2} \ G_{1}))⟩.

For each ⟨G, b⟩ ∈ G, each V ∈ ℘_{f}(Vars), and x ∈ V ars, the unary operations ∃∃\overline{V} : G → G and (x, V) ⊗^{↔}: G → G are given by

∃∃\overline{V} ⟨G, b⟩ =^{def} ⟨G \ V, ¨∃\overline{V} b⟩

and

(x, V) ⊗^{↔}⟨G, b⟩

=^{def} \{ \begin{matrix}η(G ∪ V, b[1/(V \ G)]), & if x ∈ G; \\ η(G ∪ \{x\}, b[1/x]), & if V ⊆ G; \\ η(G, b ¨∧ (x ¨↔ (V \ G))), & if x ∉ G and V ⊈ G. \end{matrix}

The following result holds almost by definition.

Theorem 104 The operations of Definition 103 are well-defined. Moreover, for each g, g_{1}, g_{2} ∈ G, each V ∈ ℘_{f}(Vars), and x ∈ Vars,

[[g_{1} ⊗ g_{2}]] = [[g_{1}]] ∧ [[g_{2}]],  [[g_{1} ⊕ g_{2}]] = [[g_{1}]] ∨ [[g_{2}]],
[∃∃\overline{V} g] = ∃\overline{V} [[g]],  [(x, V) ⊗^{↔} g] = (x ↔ ⋀(V)) ∧ [[g]].

Definition 105 (Restriction operation.) For each b ∈ B, each x ∈ Vars, and each c ∈ {0, 1} the restriction of b with respect to x and c is the BDT b[c/x] defined as follows:

0[c/x] =^{def} 0;
1[c/x] =^{def} 1;
ite(v, b_{1}, b_{0})[c/x] =^{def} \{ \begin{matrix}b_{c}[c/x], & if x = v; \\ ite(v, b_{1}[c/x], b_{0}[c/x]), & otherwise. \end{matrix}

This restriction operation on BDTs is easily seen to be the syntactic counterpart of the operation on Boolean functions given in Definition 95.

Proposition 106 For each b ∈ B, each x ∈ Vars, and each c ∈ {0, 1},

[b[c/x]] = [[b]][c/x].

Proof By induction on the structure of b. The base cases b = 0 and b = 1 are immediate from definitions 95 and 105. For the induction step, suppose b = ite(v, b_{1}, b_{0}). If x = v then

[b[c/v]] = [b_{c}[c/v]]
= [[b_{c}]][c/v]
= ite(v, [[b_{1}]], [[b_{0}]])[c/v]
= [[b]][c/v].

If x ≠ v then

[b[c/x]] = [ite(v, b_{1}[c/x], b_{0}[c/x])]
= ite(v, [b_{1}[c/x]], [b_{0}[c/x]])
= ite(v, [[b_{1}]][c/x], [[b_{0}]][c/x])
= ite(v, [[b_{1}]], [[b_{0}]])[c/x]
= [[b]][c/x].

□

Lemma 107 For each b ∈ B, each x ∈ Vars, and each c ∈ {0, 1} we have that x ∉ vars(b[c/x]).

Proof Let a ∈ A. Then

[b[c/x]](a[0/x]) = [[b]][c/x](a[0/x])
= [[b]](a[0/x][c/x])
= [[b]](a[c/x])
= [[b]](a[1/x][c/x])
= [[b]][c/x](a[1/x])
= [b[c/x]](a[1/x]). □

PROOF of Theorem 104 (sketch). We first see that, if ⟨G, b⟩ is the result of one of the operations of Definition 103, then we have true(b) = ∅. We use Lemma 107 and other simple facts about Boolean functions. Of course, we also exploit the assumption that the semantics of OBDT operations is as given in Table 6.1. For the ⊗ and (x, V) ⊗^{↔} operators this is true by the definition of η given in Equation (6.2). Also, true(∃V̄ b) ⊆ true(b), which handles the case of the ∃∃V̄ operators. For the ⊕ operator, from

true(b_{1}) = true(b_{2}) = ∅,
vars(b_{1}) ∩ G_{1} = vars(b_{2}) ∩ G_{2} = ∅,

we obtain

true(b_{1} ¨∧ ¨⋀(G_{1} \ G_{2})) = G_{1} \ G_{2},
true(b_{2} ¨∧ ¨⋀(G_{2} \ G_{1})) = G_{2} \ G_{1}.

Since, for each b′, b′′ ∈ B, true(b′ ¨∨b′′) = true(b′)∩true(b′′) the thesis follows.

Now we see that, if an operator gives back ⟨G, b⟩, then we have G ∩ vars(b) = ∅. It is immediate from the definition that any nonrecursive application η(G, b) is such that G ∩ vars(b) = ∅. Thus, by Lemma 107,

(G ∪ true(b))∩ vars(b[1/ true(b)])
= (G ∪ true(b))∩( vars(b) \ true(b))
= G∩( vars(b) \ true(b))
= ∅,

and also the recursive applications of η enjoy the same property. This completes the proof for the ⊗ and (x, V) ⊗^{↔} operators. For the ⊕ operator, just observe that

vars((b_{1} ¨∧ ¨⋀(G_{1} \ G_{2})) ¨∨ (b_{2} ¨∧ ¨⋀(G_{2} \ G_{1}))) ⊆

vars(b_{1}) ∪ vars(b_{2}) ∪ (G_{1} \ G_{2}) ∪ (G_{2} \ G_{1}).

Then, since vars(∃∃V̄ b) = vars(b)∩V and (G\V)∩(vars(b)∩V ) = ∅ we have the thesis for ∃∃V̄.

The statement about the operations’ semantics is routine. □

Notice that G operations make use of the B_{o} (ROBDD) operations only when strictly necessary. When this happens, expensive operations like ¨∧ and ¨∨ are invoked with operands of the smallest possible size. In particular, we exploit the fact that the restriction operation is relatively cheap. However, we cannot avoid searching for true variables, as the ⊗ and (x, V) ⊗^{↔} operators need that. For this purpose, the procedure implicit in Theorem 100 comes in handy. In programs where many variables are ground the ROBDDs generated will be kept small, and so also the cost of searching will be diminished. As a final remark, observe that in a real implementation the operations which are executed can be further optimized. Without entering into details, the basic analysis step, for what concerns groundness and in a bottom-up framework, generates macrooperations of the form

(x_{1}, V_{1}) ⊗^{↔}((x_{2}, V_{2}) ⊗^{↔}(⋯ ((x_{n}, V_{n}) ⊗^{↔}(g_{1} ⊗ g_{2} ⊗ ⋯ ⊗ g_{m})) ⋯)).

These operations can be greatly simplified by first collecting all the true variables in the g_{i}’s in one sweep (a bunch of set unions) and iterating through the (x_{i}, V_{i}) ⊗^{↔} indexes for collecting further true variables. Then the ROBDDs which occur in the macro-operation are restricted using the collected true variables and, at the end of this process, the ROBDD package is invoked over the simplified arguments. Only then we search for further true variables in the resulting ROBDD.

6.8 Experimental Evaluation

The ideas presented here have been experimentally validated in the context of the development of the China analyzer [Bag94]. China is a data-flow analyzer for CLP(H_{N}) languages (i.e. Prolog, CLP(R), clp(FD) and so forth) written in Prolog and C^{++}. It performs bottom-up analysis deriving information on both call and success patterns by means of program transformations and optimized fixpoint computation techniques.

The assessment of the hybrid domain has been done in a quite radical way. In fact, we have compared the standard, pure ROBDDbased implementation of Pos against the hybrid domain on the following problem: deriving, once for each clause’s evaluation, a Boolean vector indicating which variables are known to be ground and which are not. This is a very minimal demand for each analysis requiring the knowledge about definitely ground variables during the analysis. We have thus performed the analysis of a number of programs on a domain similar to Pat(Pos) [CLV94], switching off all the other domains currently supported by China^{3}. Pat(ℜ) is a generic structural domain which is parametric with respect to any abstract domain ℜ. Roughly speaking, Pat(ℜ) associates to each variable the following information:

• a pattern, that is to say, the principal functor and subterms which are bound to the variable;

• the “properties” of the variable, which are delegated to the ℜ domain (the two implementations of Pos, in our case).

As reported in [CLV93], Pat(Pos) is a very precise domain for groundness analysis.

The experimental results are reported in Table 6.2. The table gives, for each program, the analysis times and the number of ROBDD nodes allocated for the standard implementation (STD) and the hybrid one (HYB), respectively. It also shows the ratio STD/HYB for the above mentioned figures (S/H). The computation times have been taken on a 80486DX4 machine with 32 MB of RAM running Linux 1.3.64. The tested programs have become standard for the evaluation of data-flow analyzers. They are a cutting-stock program CS, the generate and test version of a disjunctive scheduling program Disj, a program to put Boolean formulas in disjunctive

3Namely, numerical bounds and relations, aliasing, and polymorphic types

Analysis time (sec)  N. of BDD nodes
Program STD HYB S/H  STD  HYB  S/H
CS  1.06 0.6  1.77 12387  391 31.7
Disj  1.06 0.6  1.77 72918  176 414.3
DNF  5.17 4.4  1.18  5782  111 52.1
Gabriel  1.13 0.74 1.53 28634 10472  2.73
Kalah  3.92 2.02 1.94 43522  645 67.5
Peep  6.13 5.52 1.11 176402 128332  1.37
PG  0.37 0.25 1.48  3732  86 43.4
Plan  0.59 0.5  1.18  1736  65 26.7

Table 6.2: Experimental results obtained with the China analyzer.

normal form DNF, the Browse program Gabriel taken from Gabriel benchmark, an alpha-beta procedure Kalah, the peephole optimizer of SB-Prolog Peep, a program PG written by W. Older to solve a particular mathematical problem, and the famous planning program Plan by D.H.D. Warren.

The results indicate that the hybrid implementation outperforms the standard one in both time and space efficiency. The systematic speed-up obtained was not expected. Indeed, we were prepared to content ourselves with a moderate slow-down which would have been recovered in the reactive combinations. The space figures show that we have achieved significant (and sometimes huge) savings in the number of allocated ROBDD nodes. With the hybrid domain we are thus able to keep the ROBDDs which are created and traversed during the analysis as small as possible. This phenomenon is responsible for the speed-up. It seems that, even for programs characterized by not-so-many ground variables, there are always enough ground variables to make the hybrid implementation competitive. This can be observed, for instance, in the case of the Peep program, which was analyzed with a non-ground, most-general input pattern. The following additional observations are important for a full understanding of Table 6.2:

1. we are not comparing against a poor standard implementation of Pos, as can be seen by comparing the analysis times with those of [CLV93]. The ROBDD package we are using is fine-tuned: it employs separate caches for the main operations (with hit-rates in the range 95%–99% for almost all programs), specialized and optimized versions of the important operations over ROBDDs, as well as aggressive memory allocation strategies. Indeed, we were led to the present work by the apparent impossibility of further optimizing the standard implementation. Moreover, the hybrid implementation has room for improvement, especially for what concerns the handling of bit-vectors.

2. We are not taking into account the cost of garbage-collection for ROBDD nodes. In particular, the sizes of the relevant data-structures were chosen so that the analysis of the tested programs could run to completion without any node deallocation or reallocation.

3. The Boolean vectors computed during our test analyses are what is necessary for, say, the quick handling of delayed constraints and goals, and the efficient simplification of aliasing information. However, the experiment does not take into account the inevitable gains which are a consequence of the fast access to ground variables. Furthermore, in a truly reactive combination, the set of ground variables is not needed only at the end of each clause’s evaluation (this is the optimistic hypothesis under which we conducted the experimentation), but at each body-atom evaluation for each clause. In this context the hybrid implementation, due to its incrementality, is even more favored with respect to the standard one (which is not incremental at all).

6.9 Conclusion

We have studied the problem, given an implementation of Pos based on ROBDDs, of determining as efficiently as possible the set of variables which are forced to true in the abstract representation. We have explained why, for the sake of realizing reactive combinations of domains, it is important to detect these variables (which correspond to ground ones at the concrete level) as quickly as possible. This problem has not been treated before in the literature [CH93, AMSS, HCC95]. After reviewing the naïf approaches, we have presented a simple method of detecting all the true variables in a ROBDD representation at once. We have then proposed a novel hybrid representation for Boolean functions. This representation is designed in a way to take advantage from the observation that most programs (together with their typical queries) have a high percentage of variables which are deemed to be ground at the program points of interest. With the new representation, not only the information about true (ground) variables is always readily available (instead of being scattered all over the ROBDDs), but we are also able to keep the usage of (expensive) ROBDDs at a minimum. This is clearly important for efficiency reasons. In fact, we have presented the experimental results obtained with a prototype implementation of the hybrid domain which outperforms, from any point of view, the standard implementation based on ROBDDs only. Surprisingly enough, we have thus been able to assess the superiority of the hybrid domain even for those cases where fast access to ground variables is not important.

Precise Detection of Call-Patterns

\underline{Contents}

7.1 Introduction . . . . . . . . . . . . . . . . . 268 7.2 The Magic-Templates Algorithm . . . . . 274 7.3 Generalized Semantics . . . . . . . . . . . 276

7.3.1 Interpretation Domains . . . . . . . . . 276 7.3.2 Functional Representations . . . . . . . 281 7.3.3 Generalized CLP Programs . . . . . . . 285 7.3.4 The Functional Semantics . . . . . . . . 286 7.3.5 Top-Down (Operational) Construction . 286 7.3.6 Bottom-Up (Fixpoint) Construction . . 289

7.4 Abstract Interpretation . . . . . . . . . . . 296 7.5 Conclusion . . . . . . . . . . . . . . . . . . 299 7.6 Proof of the Main Result . . . . . . . . . . 299

7.1 Introduction

In data-flow analysis of constraint logic programs we are often interested in deriving information about just two kinds of program points: clause’s entries and clause’s successful exits. Using a terminology that has become customary in the field, that is to say that we want to derive call-patterns and success-patterns. In other words, for each clause we want to derive properties of the constraint store that are valid

1. whenever the clause is invoked (call-patterns); and

2. whenever a computation starting with the invocation of the clause terminates with success (success-patterns).

In principle, call-patterns can be reconstructed, to a limited extent, from the success-patterns. This, however, often implies significant precision losses. As the precision of call-patterns is very important for many applications, their direct computation is desirable. Top-down analysis methods are usually advocated for this purpose, since the standard execution strategy of (constraint) logic programs is top-down. Alternatively, methods based on program transformation and bottom-up analysis can be employed. This approach, however, can result in a loss of precision because the connection between call- and success-patterns is not preserved.

In a recent work, Debray and Ramakrishnan [DR94] introduced a bottom-up analysis technique for logic programs based on program transformation. They showed, among other things, that their bottom-up analysis is at least as precise (on both call- and successpatterns) as any top-down abstract interpretation using the same abstract domain and abstract operators.

The basic idea behind the approach of Debray and Ramakrishnan is to employ a variation of the Magic Templates algorithm [Ram88]. Nothing new yet, since this follows a line of research on bottom-up analysis methods based on the Magic Sets or similar transformations [Kan93, CDY94, Nil91]. However, as several authors [CDY94, Nil91, DR94] have pointed out, a direct application of Magic Templates can result in a loss of precision. While both calland success-patterns are derived, the connection between them is lost. As a simple example, let us consider a program P containing the following CLP(R) clause, R:

         transistor_state(Beta, Ib, Ic, Ie):-
             Ib >= 0,
             Ic = Beta*Ib,
             Ie+Ib+Ic = 0.

Suppose we apply the magic transformation to P and analyze the result employing a domain for range and relations, like those presented in Chapter 5, and a domain for groundness. The analysis of P can discover two different call-patterns (perhaps coming from two different call sites) for predicate transistor_state/4:

D_{1}^{c} =^{def} (Beta > 0),
D_{2}^{c} =^{def} (Beta > 0 ∧ ground(Beta)).

Analyzing clause R with respect to these call-patterns we can obtain the success-patterns^{1}

D_{1}^{s} =^{def} (Beta > 0 ∧ Ib ≥ 0),  (7.1)
D_{2}^{s} =^{def} D_{1}^{s} ∧ (Ic ≥ 0 ∧ Ie ≤ 0).

(7.2)

Now, if the success-patterns are merged, obtaining D_{1}^{s}, we have clearly lost information. With the standard approach, i.e., by maintaining two separate relations for call- and success-patterns, even if we keep both D_{1}^{s} and D_{2}^{s}, all that can be said may be summarized as

1Notice that Ic = Beta*Ib is a non-linear constraint.

calls_{R} =^{def} {D_{1}^{c}, D_{2}^{c}},
succs_{R} =^{def} {D_{1}^{s}, D_{2}^{s}}.

(7.3)

Having lost the connection between call- and success-patterns, when confronted with a call-pattern, say,

D^{c} =^{def} (Beta = 60),

which is stronger than D_{2}^{c}, we must content ourselves with the success-pattern

D^{s} =^{def} D^{c} ∧ D_{1}^{s}.

In fact, D^{c} is “compatible” with both D_{1}^{s} and D_{2}^{s}. Stated differently, D_{1}^{s} and D_{2}^{s} are both possible as success-patterns for D^{c}. In order to obtain the strictly more precise result

E^{s} =^{def} D^{c} ∧ D_{2}^{s}

we must keep the relation between call- and success-patterns explicitly. This means that for clause R we must record, instead of the two relations given in (7.3), something like

callsuccs_{R} =^{def} {D_{1}^{c} ↦ D_{1}^{s}, D_{2}^{c} ↦ D_{2}^{s}}.  (7.4)

The intuitive reading of (7.4) is as follows:

all the concrete call-patterns of clause R are described by either D_{1}^{c} or D_{2}^{c}. Moreover, every call-pattern that is described by D_{i}^{c} gives rise to a concrete success-pattern that is described by D_{i}^{s}, for i = 1, 2.

In this particular example, all the concrete call-patterns described by D^{c} are also described by D_{1}^{c} and D_{2}^{c}. Formally:

γ(D^{c}) ⊆ γ(D_{1}^{c}) ∩ γ(D_{2}^{c}).

As a consequence, we are allowed to conclude that the ensuing success-patterns are described by both D_{1}^{s} and D_{2}^{s}. Thus we can conclude that

E^{s} = D^{c} ∧ D_{2}^{s} = D^{c} ∧ D_{1}^{s} ∧ D_{2}^{s}

is a correct success-pattern for clause R corresponding to the callpattern D^{c}.

The above example does not tell the entire story, and the reason is that we have considered an abstract domain, which, in addition, turns out to be monotonic^{2}. Indeed, when dealing with monotonic properties only, the problem can be understood as a lack of expressivity of the domain, rather than a deficiency of the transformation approach. In this case, in fact, the information loss can be avoided to the extent the domain employed can represent the dependencies of the success-patterns from the call-patterns. For the above example, the ask-and-tell combination of the numerical domain with the groundness domain (see Section 3.7 on page 112) would allow to obtain the success-pattern

E_{1}^{s} =^{def} (Beta > 0 ∧ Ib ≥ 0) ∥ ask(ground(Beta)) → tell(Ic ≥ 0 ∧ Ie ≤ 0),

instead of D_{1}^{s} as given by (7.1).

The limitations of the transformation approach are more serious when the analysis concerns (also) non-monotonic properties and, in

2Notice how the call-pattern D^{c} is “preserved” in the success-patterns D^{s} and E^{s}. This, of course, will not always happen for non-monotonic domains.

                      p(X) :-
                          q(a,X),
                          q(X,b).
                      q(X,Y).
                      ?- p(X).

Figure 7.1: Program showing some difficulties with the magic transformation.

this case, the theory developed in Chapter 3 is not applicable. The next example provides a very compelling argument: it shows that a straightforward application of magic transformation, followed by a standard bottom-up evaluation, causes information loss already at the concrete level.

Consider the program and goal in Figure 7.1.^{3} The bottom-up evaluation of the transformed program on the standard domain of Herbrand equality constraints yields the following sets of call- and success-patterns^{4}:

calls =^{def} {p(X), q(a, X), q(X, b), q(b, b)^{∗}},
succs =^{def} {p(X), p(a)^{∗}, p(b)^{∗}, q(a, X), q(X, b), q(b, b)^{∗}}.

(7.5)

Here our inability to re-couple call- and success-patterns is selfevident. However, the real problem is that the patterns marked with ‘^{∗}’ will never be computed by a top-down evaluation of the original program! This precision loss at the concrete level is clearly reflected at the abstract level. No matter how refined is the domain for freeness analysis we might employ, the fact that top-down evaluation of ?-p(X) succeeds with a free argument cannot be derived.

3This nice example is due to an anonymous referee.

4Observe that, for simplicity, we are exploiting the fact that tuples of terms constitute a normal form for Herbrand equality constraints.

The approach of [DR94], which is restricted to ordinary logic programs, solves the problems outlined above. It consists in transforming the source program by means of Magic Templates. Then an “explicated” version of the transformed program is built. In the explicated version the arguments of each clause head and body atom are duplicated: one copy represents the tuple of arguments at the moment of the call (the “calling arguments”), whereas the other copy represents the arguments at the moment of return (the “return arguments”). The “explication” of clause’s bodies makes explicit the operational aspects of the execution of logic programs: unification in particular. The solution of [DR94] is not generalizable to the entire class of CLP programs, since it exploits some peculiar properties of the Herbrand constraint system, where a tuple of terms is a strong normal form for constraints. This way, and by duplicating the arguments, they are able to describe the partial functions that represent the connection between call- and success-patterns in the clauses heads. Of course, this cannot be done for generic CLP languages.

In this work we aim at generalizing the overall result of [DR94] to the unrestricted case of constraint logic programs: “the abstract interpretation of languages with a top-down execution strategy need not itself be top-down.” A bottom-up analysis framework has some advantages over top-down ones. It is simpler, it is easier to guarantee termination without special devices such as memoization. This, however, is beyond the scope of this work.

We present a generalized framework for the semantics of the entire class of CLP languages. The approach, which is still based on program transformation and bottom-up evaluation, is able to capture both call- and success-patterns without loss of precision at the concrete level. The framework can accommodate a very wide range of non-standard semantics and, in particular, abstract interpretations. We employ domains of partial functions that are parametric with respect to a given interpretation domain. These partial functions express both call-patterns, success-patterns, and the connection between them in a very convenient way. Moreover, the entire construction is almost automatic: given any abstract domain we can define a functional representation over it and define an iteration sequence that computes the desired approximation of the abstract semantics. The only domain-dependent thing is (part of) the design of a suitable widening operator [CC77, CC92b] ensuring termination.

7.2 The Magic-Templates Algorithm

Our objective is to capture the top-down control strategy of CLP languages by means of a program transformation followed by a bottom-up evaluation. The transformation we employ is a generalization of Magic Set [BR87, BMSU86] that is applied by an instance of the Magic Templates algorithm. This algorithm is applicable to any language where programs are made up by Horn-like clauses: CLP in our case. The basic idea is to introduce auxiliary clauses, called magic clauses. They are intended to “compute” the constraints “passed” from one atom to another in the original clauses, following the control strategy of the language’s implementation model (namely, extended SLD-resolution). Roughly speaking, each magic clause describes a procedure call in the body of an original clause. The Magic Templates algorithm also prescribes the addition of an atom (the magic atom, which is defined by the magic clauses) in the body of the original clauses. This atom forces the bottom-up evaluation to take into account the conditions under which the clause is invoked. In an evaluation over the standard (or concrete) domain this can be thought of as a “filter” that reduces the search space, preventing the generation of irrelevant facts, i.e., those that cannot be produced by any execution, standing a particular set of initial goals. In a data-flow analysis setting, the added atom is what makes possible a goal-dependent analysis with the effect of improving the accuracy. The original clauses with the magic atom added are called modified clauses. Here is the Magic Templates algorithm, instantiated over CLP languages and assuming a left-to-right selection rule.^{5}

Definition 108 (Magic Templates.) [Ram88] Let P be a CLP program and G be a set of initial goals. Starting from P and G, we build a new program P_{G}^{m}. Initially P_{G}^{m} is empty. Assuming that in P no predicate symbol has ‘magic ’ as a prefix, we perform the following steps:

1. for each predicate symbol p/n in P (where n denotes the arity), create a new predicate symbol magic p/n.

2. For each clause R of P, add the modified version of R to P_{G}^{m}. Let p(t̄) be the head of R. The modified version is obtained by adding magic p(t̄) in the leftmost position of the body of R.

3. For each clause R in P and each atom q_{i}(t̄_{i}) in the body of R, add a magic clause to P_{G}^{m}: the head is magic q_{i}(t̄_{i}), whereas the body consists of all the constraints and atoms preceding q_{i}(t̄_{i}) in the modified version of R.

4. For each goal g in G and each atom q_{i}(t̄_{i}) in g, add a magic clause to P_{G}^{m}: the head is magic q_{i}(t̄_{i}) whereas the body consists of all the constraints and atoms preceding q_{i}(t̄_{i}) in g.

5For the knowledgeable reader: in our case the adorned program coincides with the original program [Ram88].

For a predicate symbol p we will use the more comfortable notation m p instead of magic p.

As an example, Magic Templates transforms the program of Figure 7.1 on page 272 into the program of Figure 7.2. For a CLP example, the CLP(N) program in Figure 5.2 on page 166 is transformed as in Figure 7.3.

7.3 Generalized Semantics

As shown in Chapter 3, the characteristics of the CLP framework [JL87] allow for the definition of a generalized algebraic semantics. However, the framework presented in Chapter 3 is limited to (possibly non-standard) interpretations based on monotonic properties. For this class of properties the similarities between the standard and non-standard interpretations are striking, as monotonicity allows to employ constraints also to represent the non-standard properties. For our purposes we need a more general framework, capturing also non-monotonic properties, which is introduced in the following sections.

7.3.1 Interpretation Domains

Here we generalize the semantic treatment for CLP languages presented in Section 3.2 on page 37. While several things carry through without change to the case where non-monotonic properties are considered, we can no longer assume that the domain of interpretation is a constraint system. We thus introduce a class of algebraic structures called interpretation domains.

The carrier of an interpretation domain is a set of properties, or descriptions. In our setting, we assume that properties deal with program variables. Properties usually describe the instantiation status of program variables, at some level of abstraction. As we did

p(X) :-                       m_q(a, X) :-
m_p(X),                       m_p(X).
q(a,X),                   m_q(X, b) :-
q(X,b).                       m_p(X),
                       q(a, X).
q(X,Y) :-                     m_p(X).
m_q(X, Y).

Figure 7.2: The “magic version” of the program in Figure 7.1.

mc(N, N-10) :-
m_mc(N, N-10),
N > 100.
mc(N, M) :-
m_mc(N, M),
N <= 100,
mc(N+11, U),
mc(U, M).
m_mc(N+11, U) :-
m_mc(N, M),
N <= 100.
m_mc(U, M) :-
m_mc(N, M),
N <= 100,
mc(N+11, U).

Figure 7.3: The “magic version” of the program in Figure 5.2 on page 166. in Section 3.2.3 on page 46, we make explicit some (very reasonable) assumptions about the relationships of properties to program variables. We still assume the existence of two denumerable sets of variable symbols, V and Λ, which are disjoint. We also denote the set of all variables by Vars =^{def} V ⊎ Λ. For a property D, we will indicate by FV (D) the set variables D talks about. The notion of renaming remains also unchanged: if a property D describes a tuple of variables X̄, D[Ȳ /X̄] is that very same property applied to Ȳ, if Ȳ and X̄ are disjoint tuples of distinct variables. The renaming [Y/X] has no effect on D if X ∉ FV (D), whereas variable capture is avoided by consistent renaming of bound variables. We will say that [Ȳ /X̄] is a renaming for D if and only if FV (D) ∩ Ȳ = ∅.

Definition 109 (Interpretation domain.) Any algebra D̄ of the form

⟨D, ⊴, ⊗, ⊕, 0, 1, {¯∃ ; ¯∃X̄}_{X̄∈Vars^{⋆}}, {d_{X̄Ȳ} }_{X̄,Ȳ ∈Vars^{⋆}}⟩

is an interpretation domain if it satisfies the following conditions:

I_{0}. D is a set of properties;

I_{1}. ⊴ is a preorder over D;

I_{2}. for each D ∈ D, D ⊴ 1;

I_{3}. ⊗ is an associative binary operator over D;

I_{4}. ⟨D, ⊕, 0⟩ is a commutative and idempotent monoid;

I_{5}. 0 is an annihilator for ⊗, namely, for each D ∈ D we have D ⊗ 0 = 0 ⊗ D = 0;

I_{6}. for each X̄ ∈ Vars^{⋆} we have ¯∃ ; ¯∃X̄: D → D and F V (¯∃ ; ¯∃X̄ D) ⊆ X̄ for each D ∈ D; I_{7}. for each X̄, Ȳ ∈ Vars^{⋆}, d_{X̄Ȳ} ∈ D and d_{X̄Ȳ} = d_{Ȳ X̄}.

The relation ⊴ is referred to as the approximation ordering of the interpretation domain. It specifies the relative precision of the properties in D: D_{1} ⊴ D_{2} means that D_{1} is more precise than D_{2} or, alternatively, that D_{2} is a safe approximation of D_{1}. The ⊕ operator models the merging of information coming from different execution paths. Its properties ensure that the merging process is insensitive to grouping, ordering and multiplicity. Moreover, empty or failed paths (which intuitively correspond to 0) are ignored. The ⊗ operator models the constraint accumulation process. The element 1 describes, intuitively, the empty constraint store, i.e., the one containing no information at all. The ¯∃ ; ¯∃X̄ operators represent the hiding process: any variable X ∉ X̄ appearing in the scope of ¯∃ ; ¯∃X̄ is isolated (hidden) from other occurrences of X outside the scope. The “complement sign” that appears on top of ¯∃ ; ¯∃ signifies that we formalize hiding in a dual way with respect to traditional approaches [SRP91, GDL95]. The distinguished elements d_{X̄Ȳ} represent parameter passing.

Indeed, interpretation domains must satisfy some other (very technical) conditions related to how they deal with variables. The interested reader can find these additional requirements in Hypothesis 121 on page 299.

Notice that the above definition specifies the bare essentials we require from a (non-standard) domain that is suitable for the abstract interpretation of CLP languages. This way we can capture abstract domains that are very weak, despite their importance for data-flow analysis. It is worth noticing that any constraint system, in the sense of Definition 7 on page 49 is an interpretation domain. The converse, of course, does not hold.

However, in a generalized framework such as the one we are describing, we need also structures that are able to capture the “concrete” (or standard) semantics of programs. Here, of course, we are much more demanding, since we want to prove the equivalence between different semantic constructions (e.g., the operational, topdown construction and the bottom-up construction). For this purpose, we now introduce a class of “strong” interpretation domains. With the domains in this class one can characterize the standard semantics of any CLP language.

Definition 110 (Strong interpretation domain.) An interpretation domain D̄ is a strong interpretation domain if and only if it satisfies the following conditions, for each X̄, Ȳ ∈ Vars^{⋆}, each renaming ρ, and D, D_{1}, D_{2} ∈ D:

S_{1}. ⊗ is commutative and idempotent;

S_{2}. D ⊗ ¯∃ ; ¯∃X̄ D = D;

S_{3}. for each family {D_{i} ∈ D}_{i∈ℕ}, ⨁_{i∈ℕ} D_{i} =^{def} D_{1} ⊕ D_{2} ⊕ ⋯ exists

and is unique in D; moreover, associativity, commutativity, and idempotence of ⊕ apply to denumerable as well as to finite families of operands;

S_{4}. ¯∃ ; ¯∃X̄(¯∃ ; ¯∃X̄ D_{1} ⊗ D_{2}) = ¯∃ ; ¯∃X̄(D_{1} ⊗ ¯∃ ; ¯∃X̄ D_{2}) = ¯∃ ; ¯∃X̄ D_{1} ⊗ ¯∃ ; ¯∃X̄ D_{2};

S_{5}. if X̄ρ = Ȳ ρ = Ȳ then ¯∃ ; ¯∃Ȳ (Dρ) = ¯∃ ; ¯∃Ȳ (D ⊗ d_{X̄Ȳ} );

S_{6}. ¯∃ ; ¯∃X̄(¯∃ ; ¯∃X̄ D) = ¯∃ ; ¯∃X̄ D, namely, ¯∃ ; ¯∃X̄ is idempotent;

S_{7}. if F V (D_{1}) ∩ F V (D_{2}) ⊆ Ȳ and F V (D_{1}) ∩ X̄ ⊆ Ȳ then

¯∃ ; ¯∃X̄(D_{1} ⊗ D_{2}) = ¯∃ ; ¯∃X̄(¯∃ ; ¯∃Ȳ D_{1} ⊗ D_{2}).

Any strong interpretation domain is a closed constraint system. Notice that here, for the first time, we needed to give a rather precise specification of the projection operators and the parameter passing elements. These conditions are clearly satisfied by any concrete constraint domain, and are necessary in order to ensure the equivalence between the semantic constructions we will present later.

7.3.2 Functional Representations

In the following sections, we will present semantic characterizations of CLP capturing both call- and success-patterns and maintaining the connection between them. For any such characterization, and given an interpretation domain D̄, the “meaning” of a program clause R is a partial function μ_{R}: D ↣ ℘(D). The idea is that descriptions appearing in the domain of μ_{R}, dom(μ_{R}), are the possible call-patterns for R, whereas, for each D ∈ dom(μ_{R}), μ_{R}(D) describes the possible outcomes of a call to R started in a constraint store described by C.

Partial functions of this kind can be conveniently represented by subsets of D × D [Bou92]. However, since we are concerned with successful computations only, we will use subsets of (D \ {0}) × D to represent the partial functions of interest. Instead of the usual notation for pairs (D^{c}, D^{s}) we will use the perspicuous notation (D^{c} ↦ D^{s}) for emphasis. Intuitively, a pair (D^{c} ↦ D^{s}) represent the set of all computations starting with a (consistent) constraint store described by D^{c} and terminating (successfully or not) in a constraint store described by D^{s}.

Before introducing the functional representation we need to define a binary predicate over properties. It expresses the notion of compatibility of two properties. In a bottom-up construction what happens is that (characterizations of) partial computations are, in a sense, concatenated in order to obtain (characterizations of) longer computations. Suppose we know that some partial computation starting with the invocation of predicate p, in the context of a constraint store satisfying D_{1}, arrives at a point where predicate q is called with a constraint store characterized by property D_{2}. Suppose we know also that, whenever predicate q is called with a constraint satisfying D_{3}, the computation succeeds with a constraint described by D_{4}. If we blindly concatenate the above partial computations, then we may obtain a computation that cannot exist in any top-down derivation. In fact, the above hypotheses do not constitute the evidence that a computation from p starting in a constraint satisfying D_{1} succeeds with a constraint satisfying D_{4}. In our construction we will require that D_{2} and D_{3} are compatible.

Definition 111 (Compatibility predicate.) Let D̄^{♮} be a strong interpretation domain. Then, ≬^{♮} ⊆ D×D is given for each D_{1}^{♮}, D_{2}^{♮} ∈ D^{♮}, by

D_{1}^{♮} ≬^{♮} D_{2}^{♮} ⟺^{def} D_{1}^{♮} = D_{2}^{♮}.

If, instead, D̄^{♯} is an interpretation domain intended to abstract another interpretation domain D̄^{♮} trough a concretization function γ: D^{♯} → ℘(D^{♮}), then ≬^{♯} ⊆ D^{♯} × D^{♯} satisfies the following safety requirement:

∀D_{1}^{♯}, D_{2}^{♯} ∈ D : ((D_{1}^{♯} ≬^{♯} D_{2}^{♯})) ∨ (γ(D_{1}^{♯}) ∩ γ(D_{2}^{♯}) = ∅).

The ≬ predicates will be used in order to limit the extent to which the bottom-up construction concatenates partial computations that are not concatenatable. This is the essence of the problem, illustrated above with the program of Figure 7.1, affecting the standard approaches employing the magic transformation followed by bottom-up evaluation.

The above definition makes clear the different notions of compatibility in the concrete case, where strong interpretation domains are used to characterize the concrete semantics, and in the abstract case. In the former case, we want a correct and complete criterion for deciding whether two partial computations can be concatenated, still obtaining a partial computation: the first computation must end up with a constraint store that is exactly the starting constraint store of the second computation.

In the latter case, when D̄^{♯} is an analysis’ domain, we must content ourselves with an approximation. Suppose that D̄^{♯} embodies both groundness and freeness information. Clearly, a partial computation ending up with a constraint where X is free cannot be prolonged with a partial computation starting with X ground, since no satisfiable constraint can make X both ground and free. If we are employing a domain for ranges and relations of and among numerical variables, we can certainly conclude that a partial computation ending with X ≥ 0 cannot be extended with another partial computation that starts with X < 0.

At the implementation level, ≬^{♯} admits different degrees of sophistication, and may involve several domains. Suppose we have domains for aliasing, freeness, and structural information. While the aliasing domain provides definite non-sharing information, the combination of structural information and freeness provides some definite sharing information. Suppose that the aliasing component of D_{1}^{♯} implies “X and Y do not share a common variable”. Suppose also that the structural component of D_{2}^{♯} contains X = f(Z) and Y = g(a, Z) and that the freeness component reveals that Z is indeed free. A refined implementation would discover this inconsistency making D_{1}^{♯} ≬^{♯} D_{2}^{♯} false. Definition 111, however, imposes only the correctness of ≬^{♯}, the precision being left to efficiency considerations that are outside the scope of the present discussion.

Definition 112 (Functional representation.) Let D̄ be an interpretation domain. The functional representation over D̄ is defined as:

D̄_{F} =^{def} ⟨D_{F}, ⊴_{F}, ⊗_{F}, 0_{F}, {¯∃ ; ¯∃X̄^{F}}_{X̄∈Vars^{⋆}}⟩,

where

D_{F} =^{def} ℘((D \ {0}) × D),
0_{F} =^{def} ∅,
¯∃ ; ¯∃X̄^{F} S =^{def} { (¯∃ ; ¯∃X̄ D^{c} ↦ ¯∃ ; ¯∃X̄ D^{s}) | (D^{c} ↦ D^{s}) ∈ S }.

The relation ⊴_{F} ⊆ D_{F} × D_{F} is given for each S_{1}, S_{2} ∈ D_{F}, by

S_{1} ⊴_{F} S_{2} ⟺^{def} ∀(D_{1}^{c} ↦ D_{1}^{s}) ∈ S_{1} :
∃(D_{2}^{c} ↦ D_{2}^{s}) ∈ S_{2} .
(D_{1}^{c} ⊴ D_{2}^{c}) ∧ (D_{1}^{s} ⊴ D_{2}^{s}).

Finally, if we define

D̃_{F} =^{def} D ∪ D_{F},

the binary operator ⊗_{F} : D̃_{F} × D̃_{F} → D̃_{F} is given as follows, for each S, S_{1}, S_{2} ∈ D_{F} and each D, D_{1}, D_{2} ∈ D:

S_{1} ⊗_{F} S_{2} =^{def} \{ D_{1}^{c} ↦ D_{1}^{s} ⊗ D_{2}^{s} | \begin{matrix}(D_{1}^{c} ↦ D_{1}^{s}) ∈ S_{1} \\ (D_{2}^{c} ↦ D_{2}^{s}) ∈ S_{2} \\ (¯∃ ; ¯∃_{FV (D_{2}^{c})} D_{1}^{s}) ≬ D_{2}^{c} \\ D_{1}^{s} ⊗ D_{2}^{s} ≠ 0 \end{matrix} \},

D ⊗_{F} S =^{def} \{ D ↦ D ⊗ D^{s} | \begin{matrix}(D^{c} ↦ D^{s}) ∈ S \\ (¯∃ ; ¯∃_{FV (D^{c})} D) ≬ D^{c} \\ D ⊗ D^{s} ≠ 0 \end{matrix} \},

S ⊗_{F} D =^{def} \{ D^{c} ↦ D^{s} ⊗ D | \begin{matrix}(D^{c} ↦ D^{s}) ∈ S \\ D^{s} ⊗ D ≠ 0 \end{matrix} \},
D_{1} ⊗_{F} D_{2} =^{def} D_{1} ⊗ D_{2}.

The notion of FV (·) and renaming are extended pointwise to functional domains, preserving all the needed properties.

It is possible to show (see Proposition 122 on page 300) that the following hold: 0_{F} is an annihilator for ⊗_{F}; ⊴_{F} is a preorder with minimum element 0_{F}; ⊗_{F} is componentwise monotonic with respect to ⊆, and completely left- and right-distributive with respect to ∪; finally, ¯∃ ; ¯∃X̄^{F} is monotonic with respect to ⊆ and continuous with respect to ∪.

7.3.3 Generalized CLP Programs

We assume, for simplicity, that clauses are of the form

p(X̄) :− {c_{1}, …, c_{n}} □ (b_{1}, …, b_{k}),

where {c_{1}, …, c_{n}} is a (conjunctive) set of atomic constraints and (b_{1}, …, b_{k}) is a sequence of atoms. Notice that the parentheses around (b_{1}, …, b_{k}) will be often omitted for simplicity. When ⊗ is commutative this assumption can be made without restrictions. The reader can easily figure out how to modify (at the price of some complications) the subsequent definitions in order to accommodate non-commutative ⊗ operators.

It is also convenient to consider, instead of a syntactic CLP(X) program P, its semantic version over some interpretation domain D̄. This is obtained by interpreting the set of constraints appearing in clauses through a function [[·]]D̄ ; C : ℘_{f}(C) → D, where C is the language of (syntactic) atomic constraints for CLP(X).

Another important simplification can be obtained if we assume, without loss of generality, that the heads of program clauses are normalized. It is for this purpose that we have an additional, totally ordered set of variable symbols, Λ = {A_{1}, A_{2}, A_{3}, … }, disjoint from V. The head of each program clause defining a predicate p/n will always be p(A_{1}, …, A_{n}), and denoted by p(Λ⃗), where Λ⃗ means “an initial segment of Λ of the appropriate length.” This device will avoid much of the burden of talking “modulo renaming”.

A CLP(D̄) program is a sequence of Horn-like formulas of the form

p(Λ⃗) :− D □ b_{1}, …, b_{k},

where D ∈ D. Given a CLP(X) program P and a constraint interpretation [[·]]D̄ ; C, the CLP(D̄) program [[P ]]D̄ ; C is given, for each r = 1, …, # P, by

[[P ]]D̄ ; C[r] = (p(Λ⃗) :− [[C]]D̄ ; C □ B) ⟺^{def} P [r] = (p(Λ⃗) :− C □ B).

7.3.4 The Functional Semantics

Given any functional domain D̄_{F}, our semantics associates an element of D_{F} to each program clause.

Definition 113 (Functional interpretation.) Let D̄ be an interpretation domain, let P be a CLP(D̄) program, and let G be a set of CLP(D̄) goals. A functional interpretation for P over D̄ is any element I of

ID̄_{F} ; P_{G} =^{def} {1, …, # P } → D_{F}

such that, for each r = 1, …, # P, if the head of P [r] is p(Λ⃗) then F V (I(r)) ⊆ Λ⃗. The operations and relations of D̄_{F} are extended pointwise to ID̄_{F} ; P_{G} in the obvious way. Functional interpretations will be represented by means of function graphs.

It is straightforward to show that all the interesting properties of functional representations lift smoothly to interpretations.

7.3.5 Top-Down (Operational) Construction

The notion of derivation for CLP(D̄) programs is quite standard. Notice that we restrict ourselves to the leftmost selection rule.

Definition 114 (Finite derivation.) Given any interpretation domain D̄, let P be a CLP(D̄) program and G be an initial goal for P. A finite derivation of P from G is a sequence of goals (G_{0}, G_{1}, …, G_{n}), together with a sequence (R_{1}, …, R_{k}) of variants of clauses in P that are renamed apart, where G_{0} = G and, for each i > 0 we have:

1. G_{i} = D_{i} □ p_{i_{1}}(X̄_{i_{1}}), …, p_{i_{n}}(X̄_{i_{n}}) with D_{i} ≠ 0;

2. R_{1}, …, R_{h} are the variants used in the derivation up to G_{i};

3. R_{h+1} = (p_{i_{1}}(Ȳ) :− D □ B) is a variant of P [r] such that^{6}

F V (R_{h+1}) ∩ (F V (G_{0}) ∪ ⋃_{j=1}^{h} F V (R_{j})) = ∅;

4. G_{i+1} = D_{i} ⊗ d_{X̄_{i_{1}} Ȳ} ⊗ D □ B :: (p_{i_{2}}(X̄_{i_{2}}), …, p_{i_{n}}(X̄_{i_{n}})).

Each transition from G_{i} to G_{i+1} is called a derivation step and is indicated by G_{i} ⇝_{P} G_{i+1}. When D̄ is the standard constraint domain for the CLP language at hand, the constraint D_{i} associated to G_{i} is called the constraint store at step i. A successful derivation is a finite derivation where the last goal is constituted by constraints only. The derivation is finitely failed otherwise.

The only important point about the operational construction is that call-patterns are recorded, by means of functional representation’s elements, together with the corresponding success-patterns.

Definition 115 (Operational semantics.) Given a domain D̄, a CLP(D̄) program P, and a set G of CLP(D̄) goals, we define

OD̄_{F}(P, G) ∈ ID̄_{F} ; P_{G} as

OD̄_{F}(P, G) =^{def} { (r, OD̄_{F}(P, G, r)) | 1 ≤ r ≤ # P },

6This static condition is sufficient to ensure correct renaming apart. This is because we assume (see Hypothesis 121 on page 299) that ⊗ does not introduce new variables: F V (D_{1} ⊗ D_{2}) ⊆ F V (D_{1}) ∪ F V (D_{2}), for each D_{1}, D_{2} ∈ D (in other words, ⊗ is relevant). If ⊗ is not relevant such a renaming-apart condition cannot be given statically, i.e., before actually starting the derivation.

where

OD̄_{F}(P, G, r) =^{def}

\begin{cases}((¯∃ ; ¯∃X̄ D^{c})[Λ⃗/X̄] ↦ \\ (¯∃ ; ¯∃X̄ D^{s})[Λ⃗/X̄]) \end{cases} | G_{0} ∈ G R = (p(Ȳ) :− D □ B_{R}) is a variant of P [r] G_{0} ⇝_{P}^{∗} D^{c} □ p(X̄) :: B D^{c} ≠ 0 \begin{aligned}D^{c} □ p(X̄) & ⇝_{\{R\}} & G′ \\ & ⇝_{P}^{∗} & D^{s} □ B′ \end{aligned} D^{s} ≠ 0 ⟹ B′ = ϵ \}.

In words, here is what happens. We take any goal G_{0} in our set of initial goals G and a suitable variant R of clause P [r]. Suppose there is a finite derivation from G_{0} to a goal where the constraint is D^{c} and the first atom, p(X̄), has the same predicate symbol, p, of P [r] (the sequence B of atoms yet to be solved after the call to p does not matter). Suppose also that D^{c} is consistent, and consider a partial derivation from D^{c} □ p(X̄) starting with an application of clause R (arriving at a configuration G′, which we do not care about), and continuing until a configuration, say, D^{s} □ B′ is reached, where B′ is the sequence of atoms yet to be solved. The last condition says an important thing about the partiality of this last derivation:

• either D^{s} = 0, in which case the derivation is failed (B′ might be non-empty or not); nonetheless, clause P [r] has been invoked with call-pattern D^{c};

• or B′ = ϵ, in which case the derivation is indeed total and successful; D^{s} is thus the success-pattern corresponding to an invocation of clause P [r] with call-pattern D^{c}.

Notice that properties are normalized using the distinguished variable symbols in Λ.

7.3.6 Bottom-Up (Fixpoint) Construction

Given CLP(X) program P, a set of CLP(X) goals G, and an interpretation domain D̄, let P_{G} be the generalized CLP(D̄) program obtained by transforming P as specified by Definition 108 and interpreting the constraints over D̄. The bottom-up construction for P is defined over P_{G}.

For a program P and each i = 1, …, # P we assume that P_{G}[i] is the modified clause corresponding to P [i]. Moreover, we represent the bodies of the clauses of P_{G} as a triple: magic atom (if any), property (describing the original clause’s constraints), and original body atoms. Thus, each clause of P_{G} has the general form

p̂(Λ⃗) :− (M, D, B)

where p̂ is a (possibly magic) atom, M is a set of magic atoms that is either empty or a singleton, D ∈ D is a property, and B is a finite, possibly empty sequence of atoms. This representation allows us to generalize the following three cases: modified clauses,

p(Λ⃗) :− ({m p(Λ⃗)}, D, (p_{1}(X̄_{1}), …, p_{n}(X̄_{n})));

magic clauses arising from the transformation of a goal,

m p(Λ⃗) :− (∅, D, (p_{1}(X̄_{1}), …, p_{n}(X̄_{n})));

and other magic clauses,

m p(Λ⃗) :− ({m q(X̄)}, D, (p_{1}(X̄_{1}), …, p_{n}(X̄_{n}))).

Remember that our set of program variables is Vars = V ⊎ Λ, where Λ and V are totally ordered. For Λ⃗ ∈ Λ^{⋆} and W ⊆_{f} Vars, we denote by Ȳ ≪Λ⃗ W the fact that, with respect to the ordering of V, Ȳ is a tuple of distinct consecutive variables in V such that # Ȳ = # Λ⃗ and the first element of Ȳ immediately follows the greatest variable in W.

Definition 116 (Interpretation transformer.) Let P be any CLP(X) program and G a set of CLP(X) goals. Let D̄_{F} be the functional representation over some domain D̄. Let P_{G} be the CLP(D̄) program obtained by applying the Magic Templates algorithm to P and G and interpreting the result over D̄ through [[·]]D̄ ; C. The operator induced by P_{G} over ID̄_{F} ; P_{G},

T D̄_{F} ; P_{G}: ID̄_{F} ; P_{G} → ID̄_{F} ; P_{G},

is given by

T D̄_{F} ; P_{G}(I) =^{def} { (r, T D̄_{F} ; P_{G}(r, I)) | 1 ≤ r ≤ # P_{G} }, for each I ∈ ID̄_{F} ; P_{G},

where T D̄_{F} ; P_{G}: {1, …, # P_{G}} × ID̄_{F} ; P_{G} → ID̄_{F} ; P_{G} is defined as follows: if

P_{G}[r] = p̂(Λ⃗) :− (M, D, (p_{1}(X̄_{1}), …, p_{n}(X̄_{n}))),

then

T D̄_{F} ; P_{G}(r, I) =^{def}

⋃ \{ ¯∃ ; ¯∃Λ⃗^{F} η_{r}(S) | \begin{matrix}if M = \{m q(X̄)\} \\ P_{G}[r_{0}] = m q(Λ⃗_{0}) :− T_{0} \\ Ȳ ≪Λ⃗_{0} F V (P_{G}[r]) ∪ Λ \\ S_{0} = ϖ_{r}(I(r_{0}), Λ⃗_{0}, X̄, Ȳ ) \\ else S_{0} = \{1 ↦ 1\} \\ for each i = 1, …, n: \\ P_{G}[r_{i}] = p_{i}(Λ⃗_{i}) :− T_{i} \\ Ȳ_{i} ≪Λ⃗_{i} F V (P_{G}[r]) ∪ Λ \\ ∪ Ȳ ∪ ⋃_{k=1}^{i−1} Ȳ_{k} \\ S_{i} = (I(r_{i})[Ȳ_{i}/Λ⃗_{i}]) \\ S = \overleftarrow{⊗}_{F}(S_{0}, D, d_{X̄_{1}Ȳ_{1}}, S_{1}, \\ …, d_{X̄_{n}Ȳ_{n}}, S_{n}) \end{matrix} \},

where \overleftarrow{⊗}_{F} : D̃⋆_{F} → D̃_{F} performs the left-associative composition, through ⊗_{F}, of the sequence of descriptions constituting its argument. The auxiliary function ϖ_{r}: D̄_{F} ×(Vars^{⋆})^{3} ↣ D̄_{F} is given, for each S ∈ D_{F}, and each Λ⃗_{0}, X̄, Ȳ ∈ Vars^{⋆} of the same cardinality, by

ϖ_{r}(S, Λ⃗_{0}, X̄, Ȳ) =^{def} \{ \begin{matrix}S[Ȳ /Λ⃗_{0}] ⊗_{F} d_{X̄Ȳ} , & if P_{G}[r] is magic; \\ S, & if P_{G}[r] is modified. \end{matrix}

The function η_{r}: D̃_{F} → D̄_{F}, instead, is such that, for each D ∈ D,

η_{r}(D) =^{def} {D ↦ D},

whereas for each S ∈ D_{F} we have

η_{r}(S) =^{def} { (D^{s} ↦ D^{s}) | (D^{c} ↦ D^{s}) ∈ S },

if P_{G}[r] is magic, and

η_{r}(S) =^{def} S,

if P_{G}[r] is modified.

Notice that we have provided a uniform treatment for both the magic and modified clauses. This required the introduction of the functions η_{r} and ϖ_{r} that encapsulate all the differences in the treatment of the two kinds of clauses. While η_{r} is used to make the success-pattern of magic clauses act as a call-pattern for modified clauses (the magic clauses serve the only purpose of characterizing the call-conditions for the original clauses), ϖ_{r} is used to rename and to performing parameter passing for the call-success pairs associated to magic atoms, but only in the body of magic clauses. Here is a more “operational” explanation of how T D̄_{F} ; P_{G}(r, I) is computed.

1. Take a normalized version of the r-th clause in P_{G}. This can be a modified clause (if 1 ≤ R ≤ # P), or it can be a magic clause (if # P < r ≤ # P_{G}).

2. If the r-th clause has no magic atom in the body (M = ∅), the call-description S_{0} is simply {1 ↦ 1}. Skip to step 7.

3. Otherwise M must be a singleton, say, {m q(X̄)}. If r corresponds to a modified clause, then m q(X̄) is indeed m p(Λ⃗). If, instead, r corresponds to a magic clause, then m q(X̄) can be different from m p(Λ⃗).

4. Take a normalized version of any magic clause describing the magic predicate m q. Suppose this is a version of the r_{0}-th clause.

5. Take a tuple of variables whose cardinality is the same as the the arity of q (same cardinality of Λ⃗_{0}), disjoint from the free variables occurring in the version of the r-th clause selected at step 1, and without variables belonging to the special set of variables Λ.

6. Take the functional description associated to r_{0} in the current interpretation I and call it S̃_{0}. If r corresponds to a magic clause S̃_{0} needs to be renamed and to have the parameter passing element added to it. If r corresponds to a clause S̃_{0} needs nothing. Let S_{0} be the outcome of this step.

7. Choose clauses r_{1}, …, r_{n} to “resolve” against the other atoms in the body of the r-th clause.

8. For each i = 1, …, n, let S_{i} the description associated to the r_{i}-th clause in the current interpretation I, suitably renamed.

9. Put the pieces together applying ⊗_{F} in a left-associative way. Call S the result.

10. If r corresponds to a magic clause, transform S suitably: calls to magic clauses are meaningless, whereas the successes of magic clauses represent calls to ordinary clauses. If r, corresponds to a modified clause, do nothing.

11. Project the outcome of step 10 onto the variables Λ⃗. Put the result into the set of functional descriptions we are building.

12. Repeat until all the choices for r_{0}, r_{1}, …, r_{n} in the above process have been made in all the possible ways. This union of all the description so obtained is the new interpretation for the r-th clause.

Now, the concrete iteration sequence [CC92b] that is used for the bottom-up construction of the semantics is inductively defined as follows, for each ordinal κ ∈ 𝕆:

T D̄_{F} ; P_{G} ↑0 =^{def} 0_{F},
T D̄_{F} ; P_{G} ↑(κ + 1) =^{def} T D̄_{F} ; P_{G}(T D̄_{F} ; P_{G} ↑κ),
T D̄_{F} ; P_{G} ↑κ =^{def} ⋃_{β<κ}(T D̄_{F} ; P_{G} ↑β),

when κ > 0 is a limit ordinal.

The ⊆ relation is the computational order of the functional representation [CC92b], capturing the relationship between successive iterates in the iteration sequence. Notice that this is one of the cases in abstract interpretation where the approximation and computational orderings are different. In [Scu96] it is shown that T D̄_{F} ; P_{G}

is continuous on the complete lattice (ID̄_{F} ; P_{G}, ⊆). This implies the

existence of the least fixpoint of T D̄_{F} ; P_{G} and allows us to define the result of the bottom-up semantic construction as

F D̄_{F} ; P_{G} =^{def} lfp(T D̄_{F} ; P_{G}) = T D̄_{F} ; P_{G} ↑ω.  (7.6)

Moreover, we have the following important result.

Theorem 117 (Equivalence.) Let D̄ be a strong interpretation domain and D̄_{F} the functional representation over D̄. Given a CLP(X) program P̃, a set G̃ of CLP(X) goals, and a constraint interpretation [[·]]D̄ ; C, let P and G be the CLP(D̄) program and goals obtained from P̃ and G̃ through [[·]]D̄ ; C. Let also P_{G} be the CLP(D̄) program obtained from P̃, G̃, and [[·]]D̄ ; C as in Definition 116. Then, if we define σ: ID̄_{F} ; P_{G} → ID̄_{F} ; P_{G} as

λI ∈ ID̄_{F} ; P_{G} . λr ∈ {1, …, # P } .

{ (D^{c} ↦ D^{s}) | (D^{c} ↦ D^{s}) ∈ I(r), D^{s} ≠ 0 } we have

σ(OD̄_{F}(P, G)) = F D̄_{F} ; P_{G} ↾ {1, …, # P }.

Moreover, if we denote by calls(r) the subset of {# P +1, …, # P_{G}} containing the indices of the magic clauses corresponding to calls of the predicate defined by the r-th clause, then, for each r = 1, …, # P,

(D ↦ 0) ∈ OD̄_{F}(P, G)(r) ⟹

∃r′ ∈ calls(r) . (D ↦ D) ∈ F D̄_{F} ; P_{G}(r′).

What σ does is just to strip the failure call-patterns, i.e., elements of the form (D^{c} ↦ 0), from the functional interpretation obtained through the top-down construction. The restriction to {1, …, # P } in the first statement is also necessary, since the transformed program contains also the magic clauses that were used to derive callpatterns.

Roughly speaking, a call-pattern is said to be correct if it brings to at least one successful computation [GM92]. Of course, observing correct call-patterns only (instead of any call-pattern) does not provide much information for the typical applications of data-flow analysis. The first statement of the theorem says that all and only the possibly correct call-patterns^{7} for the r-th original clause can be found in F D̄_{F} ; P_{G}(r). The second statement specifies that any other

call-pattern for the r-th clause is contained in F D̄_{F} ; P_{G}(r′), for some index r′ such that, if the r-th clause defines predicate p, then the r′-th clause defines predicate m p.

We have thus a semantic construction that, for the purpose of capturing both call- and success-patterns, is as good as the operational one. Moreover, this construction can be easily abstracted for the purpose of data-flow analysis. This is the subject of the following section.

7If D̄ is an abstract domain we might not be able to detect all the failures.

7.4 Abstract Interpretation

The problem of ensuring the correctness of the analysis is dealt with much in the same way as in Section 3.2.5 on page 57.

Definition 118 (Abstraction function.) Let D̄^{♮} and D̄^{♯} be two interpretation domains. A function α: D^{♮} → D^{♯} is an abstraction function of D̄^{♮} into D̄^{♯} if and only if:

A_{1}. α is a semi-morphism, namely, for each D^{♮}, D_{1}^{♮}, D_{2}^{♮} ∈ D^{♮} and X̄, Ȳ ∈ Vars^{⋆}:

α(D_{1}^{♮} ⊗^{♮} D_{2}^{♮}) ⊴^{♯} α(D_{1}^{♮}) ⊗^{♯} α(D_{2}^{♮}),
α(D_{1}^{♮} ⊕^{♮} D_{2}^{♮}) ⊴^{♯} α(D_{1}^{♮}) ⊕^{♯} α(D_{2}^{♮}),
α(0^{♮}) ⊴^{♯} 0^{♯},
α(¯∃ ; ¯∃♮ ; X̄ D^{♮}) ⊴^{♯} ¯∃ ; ¯∃♯ ; X̄ α(D^{♮}),
α(d_{X̄Ȳ}^{♮} ) ⊴^{♯} d_{X̄Ȳ}^{♯};

A_{2}. for each increasing chain {D_{j}^{♮} ∈ D^{♮}}_{j∈ℕ}, and each D^{♯} ∈ D^{♯},

∀j ∈ ℕ : α(D_{j}^{♮}) ⊴^{♯} D^{♯} ⟹ α( \begin{matrix}⨁ & ♮ \\ j∈ℕ \end{matrix} D_{j}^{♮}) ⊴^{♯} D^{♯};

A_{3}. for each D^{♮} ∈ D^{♮} and each renaming [Ȳ /X̄] for D^{♮}, we have

α(|D^{♮})[Ȳ /X̄] = α(D^{♮}[Ȳ /X̄]).

Each abstraction function α: D^{♮} → D^{♯} is extended to α: D_{F}^{♮} → D_{F}^{♯} pointwise: for each S ∈ D_{F}^{♮},

α(S) =^{def} { (α(D_{1}^{♮}) ↦ α(D_{2}^{♮})) | (D_{1}^{♮} ↦ D_{2}^{♮}) ∈ S }.  (7.7)

It can be shown that the extended function is an abstraction function along the lines of Definition 118.

When we use a functional representation as an abstract domain for data-flow analysis, we must ensure termination. If the domain that is used as a base for the functional construction is finite, also the corresponding functional representation is so. In any case we guarantee or accelerate termination by using widening operators [CC77, CC92b]. Here we give a simplified definition of this device for convergence enforcement and acceleration, in the case of the functional representation.

Definition 119 (Widening.) [CC77] Given a functional representation D̄♯_{F}, a binary operator ∇♯_{F} : D_{F}^{♯} → D_{F}^{♯} is called a widening

for D̄♯_{F} if

W_{1}. for each S_{1}, S_{2} ∈ D_{F}^{♯} we have S_{1} ⊴_{F} S_{1} ∇♯_{F} S_{2} and S_{2} ⊴_{F}

S_{1} ∇♯_{F} S_{2};

W_{2}. for each increasing chain S_{0} ⊆ S_{1} ⊆ S_{2} ⊆ S_{3} ⊆ ⋯, the sequence given by S′_{0} =^{def} S_{0} and, for n ≥ 1, S′_{n} =^{def} S′_{n−1} ∇♯_{F} S_{n}, is stationary after some k ∈ ℕ.

The following result is an application of a theorem in [CC92b, Proposition 6.20]. The soundness of the approach clearly follows.

Theorem 120 Let P and G be a CLP(X) program and a set of CLP(X) goals, respectively. Let D̄^{♮} and D̄^{♯} be two interpretation domains, and let [[·]]D̄^{♮} ; C and [[·]]D̄^{♯} ; C be two constraint interpretations for C (the language of constraints of X). Let α: D̄^{♮} → D̄^{♯} be an abstraction function such that α ∘ [[·]]D̄^{♮} ; C ⊴^{♯} [[·]]D̄^{♯} ; C. Let P^{♮} be the CLP(D̄^{♮}) program obtained by applying the Magic Templates algorithm to P and G and interpreting the result over D̄ through [[·]]D̄^{♮} ; C. Likewise, P^{♯} is obtained from P̃, G̃, and [[·]]D̄^{♯} ; C. Let also ∇♯_{F} a widening operator over D̄♯_{F}. Consider the abstract iteration sequence for P^{♯} with widening ∇♯_{F} inductively defined by

\{ \begin{matrix}T D̄^{♯} ; P^{♯} ⇑ 0 & =^{def} & 0♯_{F}, \\ T D̄^{♯} ; P^{♯} ⇑ (k + 1) & =^{def} & (T D̄^{♯} ; P^{♯} ⇑ k) ∇♯_{F} T D̄^{♯} ; P^{♯}(T D̄^{♯} ; P^{♯} ⇑ k), for k ∈ ℕ. \end{matrix}(7.8)

The abstract iteration is eventually stable after ℓ ∈ ℕ steps and

α(lfp(T D̄^{♮} ; P^{♮})) = α(T D̄^{♮} ; P^{♮} ↑ ω) ⊴♯_{F} (T D̄^{♯} ; P^{♯} ⇑ ℓ).

Thus we have (again) an abstract compilation approach, where the soundness of the compilation function [[·]]D̄^{♯} ; C is expressed by the re-

quirement α ∘ [[·]]D̄^{♮} ; C ⊴^{♯} [[·]]D̄^{♯} ; C

Concerning the precision of the analysis, observe that the abstract iteration given above, is much simplified. In reality, especially with “domains” like the functional ones we employ, one uses a family of widening operators. In principle, it is possible to use a different widening operator at each iteration step. Roughly speaking, widening operators coming early in the sequence do not lose much precision. Later in the sequence, when the shape of the fixpoint is more likely to have been established, more aggressive widening operators come into play [Bou92]. There is not much more to say, in general, since the design of widening operators (and the overall precision of the analysis) is highly dependent from the actual domain employed (see, e.g., [CH78]).

7.5 Conclusion

We have presented a generalized framework for the semantics of the entire class of CLP languages. The approach, which is based on the Magic Templates program transformation and bottom-up evaluation, is able to capture both call- and success-patterns without loss of precision. The framework can accommodate a very wide range of non-standard semantics and, in particular, abstract interpretations. A remarkable feature is that the entire construction is almost automatic: given any abstract domain we provide a standard way of upgrading it and using it to obtain a precise bottom-up computation of the abstract semantics. The only thing that must be dealt with in a domain-dependent way is providing suitable widening operators so to ensure the convergence of the method.

7.6 Proof of the Main Result

In this section we present the (overly complicated) proof of the main result of this chapter. It would be very nice to find a simpler proof, of course.

As mentioned previously, Definition 109 on page 278 does not specify some important, though very reasonable, requirements that interpretation domains must satisfy. Here we make them explicit.

Hypothesis 121 (Further axioms for i.d.) The operators of an interpretation domain D̄ “do not invent new free variables”. That is to say that, for each X̄, Ȳ ∈ Vars^{⋆} and each C_{1}, C_{2} ∈ D̄:

H_{1}. we have

FV (C_{1} ⊗ C_{2}) ⊆ FV (C_{1}) ∪ FV (C_{2}),
FV (C_{1} ⊕ C_{2}) ⊆ FV (C_{1}) ∪ FV (C_{2});

H_{2}. FV (d_{X̄Ȳ} ) ⊆ X̄ ∪ Ȳ. Moreover, the operators are generic, or, in other words, they are insensible to variables’ names: H_{3}. if [Ȳ /X̄] is a renaming for both C_{1} and C_{2}, then

(C_{1} ⊗ C_{2})[Ȳ /X̄] = C_{1}[Ȳ /X̄] ⊗ C_{2}[Ȳ /X̄]

and likewise for the other operators; H_{4}. if [Ȳ /X̄] is a renaming for C, then

(¯∃ ; ¯∃X̄ C)[Ȳ /X̄] = ¯∃ ; ¯∃Ȳ (C[Ȳ /X̄]);

H_{5}. if [Z̄/Ȳ] is a renaming for C, then

(C[X̄/Ȳ])[Z̄/X̄] = C[Z̄/Ȳ];

H_{6}. if [Z̄/Ȳ] is a renaming for C, X̄ ∩ Ȳ = ∅, and Z̄ ∩ X̄ = ∅,

then

¯∃ ; ¯∃X̄ C = ¯∃ ; ¯∃X̄(C[Z̄/Ȳ]).

H_{7}. if [Ȳ /X̄] is a renaming for both C_{1} and C_{2}, then

C_{1} ⊴ C_{2} ⟹ C_{1}[Ȳ /X̄] ⊴ C_{2}[Ȳ /X̄].

Proposition 122 (Functional representation’s properties.) Let D̄ be any domain, and D̄_{F} the functional representation built over it. Then the following properties hold:

1. for each S ∈ D_{F} we have 0_{F} ⊗_{F} S = S ⊗_{F} 0_{F} = 0_{F};

2. ⟨D_{F}, ⊴_{F}⟩ is a preorder with minimum element 0_{F}; 3. ⊗_{F} : D_{F} × D_{F} → D_{F} (i.e., ⊗_{F} restricted to D_{F}) is componen-

twise monotonic with respect to ⊆ and completely left- and right-distributive with respect to ∪;

4. ¯∃ ; ¯∃X̄^{F} is monotonic with respect to ⊆ and continuous with respect to ∪;

5. for each family {S_{j} ∈ D_{F}}_{j∈ℕ} and each S ∈ D_{F},

∀j ∈ ℕ : S_{j} ⊴_{F} S ⟹ ⋃_{j∈ℕ}S_{j} ⊴_{F} S.

Proof The statements are all trivial consequences of the definition. We just show that ⊗_{F} is completely distributive with respect to ∪. Consider a family {S_{i} ∈ D_{F}}_{i∈ℕ}. We must show that

S ⊗_{F} ⋃_{i∈ℕ}S_{i} = ⋃_{i∈ℕ}(S ⊗_{F} S_{i})

and

(⋃_{i∈ℕ}S_{i}) ⊗_{F} S = ⋃_{i∈ℕ}(S_{i} ⊗_{F} S).

For each k ∈ ℕ, from S_{k} ⊆ ⋃_{i∈ℕ} S_{i} and the monotonicity of ⊗_{F} with respect to ⊆ we get

(S ⊗_{F} S_{k}) ⊆ S ⊗_{F} (⋃_{i∈ℕ}S_{i}),

and thus

⋃_{i∈ℕ}(S ⊗_{F} S_{i}) ⊆ S ⊗_{F} (⋃_{i∈ℕ}S_{i}).

By the same kind of reasoning we derive also

⋃_{i∈ℕ}(S_{i} ⊗_{F} S) ⊆ (⋃_{i∈ℕ}S_{i}) ⊗_{F} S.

For the reverse inclusions, by definition of ⊗_{F} we have that for each (D^{c} ↦ D^{s}) ∈ S ⊗_{F} ⋃_{i∈ℕ} S_{i} there must exists h ∈ ℕ such that (D^{c} ↦ D^{s}) ∈ S ⊗_{F} S_{h}. We can thus conclude that

S ⊗_{F} (⋃_{i∈ℕ}S_{i}) ⊆ ⋃_{i∈ℕ}(S ⊗_{F} S_{i}),

and, by a similar argument, that

(⋃_{i∈ℕ}S_{i}) ⊗_{F} S ⊆ ⋃_{i∈ℕ}(S_{i} ⊗_{F} S).

□

Lemma 123 For each C_{1}, C_{2} ∈ D and each X̄, Ȳ , Z̄ ∈ Vars^{⋆}, if the conditions

FV (C_{1}) ∩ FV (C_{2}) = ∅,  FV (C_{1}) ∩ X̄ ⊆ Ȳ ,
FV (C_{1}) ∩ Z̄ = ∅,

hold, then ¯∃ ; ¯∃X̄(C_{1} ⊗ d_{Ȳ Z̄} ⊗ C_{2}) = ¯∃ ; ¯∃X̄(¯∃ ; ¯∃Ȳ C_{1} ⊗ d_{Ȳ Z̄} ⊗ C_{2}).

Proof By properties H_{1} and H_{2} of Hypothesis 121 we have that

FV (d_{Ȳ Z̄} ⊗ C_{2}) ⊆ FV (C_{2}) ∪ Ȳ ∪ Z̄,

then

FV (C_{1}) ∩ FV (d_{Ȳ Z̄} ⊗ C_{2})
⊆ (FV (C_{1}) ∩ FV (C_{2})) ∪ (FV (C_{1}) ∩ Ȳ ) ∪ (FV (C_{1}) ∩ Z̄)
= FV (C_{1}) ∩ Ȳ
[since FV (C_{1}) ∩ FV (C_{2}) = ∅ and FV (C_{1}) ∩ Z̄ = ∅]
⊆ Ȳ

Thus, since FV (C_{1})∩X̄ ⊆ Ȳ, condition S_{7} of Definition 110 implies the thesis. □

Lemma 124 For each C_{1}, C_{2} ∈ D and each X̄, Ȳ , Z̄ ∈ Vars^{⋆}, if the conditions

FV (C_{1}) ∩ Ȳ = ∅,  Ȳ ∩ X̄ = ∅,
FV (C_{2}) ∩ Z̄ = ∅,

hold, then

¯∃ ; ¯∃X̄(C_{1} ⊗ d_{Ȳ Z̄} ⊗ ¯∃ ; ¯∃Ȳ C_{2}) = ¯∃ ; ¯∃X̄(C_{1} ⊗ ¯∃ ; ¯∃Z̄(C_{2}[Z̄/Ȳ])).

Proof By properties H_{1} and H_{2} of Hypothesis 121 we have that

FV (d_{Ȳ Z̄} ⊗ ¯∃ ; ¯∃Ȳ C_{2}) ⊆ Ȳ ∪ Z̄,

and then

FV (C_{1}) ∩ FV (d_{Ȳ Z̄} ⊗ ¯∃ ; ¯∃Ȳ C_{2}) ⊆ FV (C_{1}) ∩ (Ȳ ∪ Z̄)
= (FV (C_{1}) ∩ Ȳ ) ∪ (FV (C_{1}) ∩ Z̄)
= FV (C_{1}) ∩ Z̄
[since FV (C_{1}) ∩ Ȳ = ∅]
⊆ Z̄.

Moreover,

FV (d_{Ȳ Z̄} ⊗ ¯∃ ; ¯∃Ȳ C_{2}) ∩ X̄ ⊆ (Ȳ ∪ Z̄) ∩ X̄
= (Ȳ ∩ X̄) ∪ (Z̄ ∩ X̄)
= Z̄ ∩ X̄
[since Ȳ ∩ X̄ = ∅]
⊆ Z̄.

Then, by condition S_{7} of Definition 110, we can write:

¯∃ ; ¯∃X̄(C_{1} ⊗ d_{Ȳ Z̄} ⊗ ¯∃ ; ¯∃Ȳ C_{2}) = ¯∃ ; ¯∃X̄(C_{1} ⊗ ¯∃ ; ¯∃Z̄(d_{Ȳ Z̄} ⊗ ¯∃ ; ¯∃Ȳ C_{2}))

= ¯∃ ; ¯∃X̄(C_{1} ⊗ ¯∃ ; ¯∃Z̄((¯∃ ; ¯∃Ȳ C_{2})[Z̄/Ȳ]))

[by S_{5} with [Z̄/Ȳ] = ρ]

= ¯∃ ; ¯∃X̄(C_{1} ⊗ ¯∃ ; ¯∃Z̄(¯∃ ; ¯∃Z̄(C_{2}[Z̄/Ȳ])))

[by H_{4} since FV (C_{2}) ∩ Z̄ = ∅]

= ¯∃ ; ¯∃X̄(C_{1} ⊗ ¯∃ ; ¯∃Z̄(C_{2}[Z̄/Ȳ])).

[by S_{6} (i.e., idempotence)]

□ Lemma 125 For each C_{1}, C_{2} ∈ D, each X̄, Ȳ , Z̄ ∈ Vars^{⋆}, and each renaming ρ such that Ȳ ρ = Z̄ρ = Z̄

¯∃ ; ¯∃X̄(C_{1} ⊗ d_{Ȳ Z̄} ⊗ ¯∃ ; ¯∃Z̄(C_{1}ρ) ⊗ C_{2}) = ¯∃ ; ¯∃X̄(C_{1} ⊗ d_{Ȳ Z̄} ⊗ C_{2}).

Proof By S_{5} we have ¯∃ ; ¯∃Z̄(C_{1}ρ) = ¯∃ ; ¯∃Z̄(C_{1} ⊗ d_{Ȳ Z̄}), thus

¯∃ ; ¯∃X̄(C_{1} ⊗ d_{Ȳ Z̄} ⊗ ¯∃ ; ¯∃Z̄(C_{1}ρ) ⊗ C_{2})

= ¯∃ ; ¯∃X̄(C_{1} ⊗ d_{Ȳ Z̄} ⊗ ¯∃ ; ¯∃Z̄(C_{1} ⊗ d_{Ȳ Z̄}) ⊗ C_{2})

= ¯∃ ; ¯∃X̄(C_{1} ⊗ d_{Ȳ Z̄} ⊗ C_{2})

[by S_{2}] □ Lemma 126 For each initial goal G_{0}, consider the partial derivation

\begin{aligned}G_{0} & ⇝_{P}^{∗} Ĉ^{c} □ p(X̄) :: B \\ & ⇝_{\{R_{k}\}} Ĉ^{c} ⊗ d_{X̄Ȳ} ⊗ Cρ □ (p_{1}(Ȳ_{1}), …, p_{n}(Ȳ_{n})) :: B \\ & ⇝_{P}^{∗} Ĉ^{s} □ (p_{i}(Ȳ_{i}), …, p_{n}(Ȳ_{n})):: B, \end{aligned} (7.9)

where

Ĉ^{s} = Ĉ^{c} ⊗ d_{X̄Ȳ} ⊗ Cρ ⊗ ⨂_{i=1}^{n} (d_{Ȳ_{i}X̄_{i}} ⊗ C_{i}).

and R_{k} ≡ p(Ȳ) :− Cρ □ p_{1}(Ȳ_{1}), …, p_{n}(Ȳ_{n}) is a variant of P [r] such that FV (R_{k}) ∩ (FV (G_{0}) ∪ ⋃_{j=1}^{k−1} FV (R_{j})) = ∅, and Ūρ = Ȳ, Ū_{1}ρ = Ȳ_{1}, …, Ū_{n}ρ = Ȳ_{n}. Then,

\begin{aligned}¯∃ ; ¯∃X̄ Ĉ^{s} & = ¯∃ ; ¯∃X̄(Ĉ^{c} ⊗ d_{X̄Ȳ} ⊗ Cρ ⊗ ⨂_{i=1}^{n} (d_{Ȳ_{i}X̄_{i}} ⊗ C_{i})) \\ & = ¯∃ ; ¯∃X̄(¯∃ ; ¯∃X̄ Ĉ^{c} ⊗ d_{X̄Ȳ} ⊗ Cρ ⊗ ⨂_{i=1}^{n} (d_{Ȳ_{i}X̄_{i}} ⊗ ¯∃ ; ¯∃X̄_{i} C_{i})). \end{aligned} (7.10)

Proof By the derivation (7.9) there must exist derivations

\begin{aligned}1 □ p_{i}(Ȳ_{i}) & ⇝_{\{R_{k+h_{i}}\}} 1 ⊗ d_{Ȳ_{i}X̄_{i}} ⊗ C′_{i}ρ_{i} □ B_{r_{i}} \\ & ⇝_{P}^{∗} d_{Ȳ_{i}X̄_{i}} ⊗ C_{i} □ ∅ \end{aligned}  (7.11)

such that h_{i} > 0 and R_{k+h_{i}} = (p_{i}(X̄_{i}) :− C′_{i}ρ_{i} □ B_{r_{i}}) is a variant of P [r_{i}] with FV (R_{k+h_{i}}) ∩ (FV (G_{0}) ∪ ⋃_{j=1}^{k+h_{i}−1} FV (R_{j})) = ∅. Obviously, h_{i} < h_{i+1}. Observe also that in (7.11) we have used the same variants employed in (7.9). This implies that FV (C_{i}) ∩ Ȳ_{i} = ∅.

Consider the expressions

¯∃ ; ¯∃X̄ Ĉ^{s} = ¯∃ ; ¯∃X̄(Ĉ^{c} ⊗ d_{X̄Ȳ} ⊗ \overbrace{Cρ ⊗ ⨂_{i=1}^{n} (d_{Ȳ_{i}X̄_{i}} ⊗ C_{i})}^{E})
= ¯∃ ; ¯∃X̄(Ĉ^{c} ⊗ d_{X̄Ȳ} ⊗ E).  (7.12)

We apply Lemma 123 to (7.12). The hypotheses are readily checked exploiting point 3 of Definition 114 on page 286:

FV (C) ∩ X̄ ⊆ X̄;
FV (C) ∩ FV (E) ⊆ ( ⋃_{j=1}^{k−1} FV (R_{j})) ∩ ( ⋃_{j=k}^{N} FV (R_{j}))
= ∅;
FV (C) ∩ Ȳ ⊆ ( ⋃_{j=1}^{k−1} FV (R_{j})) ∩ ( ⋃_{j=k}^{N} FV (R_{j}))
= ∅.

Thus we obtain

¯∃ ; ¯∃X̄ Ĉ^{s} = ¯∃ ; ¯∃X̄(¯∃ ; ¯∃X̄ Ĉ^{c} ⊗ d_{X̄Ȳ} ⊗ E)
= ¯∃ ; ¯∃X̄(¯∃ ; ¯∃X̄ Ĉ^{c} ⊗ d_{X̄Ȳ} ⊗ Cρ ⊗ ⨂_{i=1}^{n} (d_{Ȳ_{i}X̄_{i}} ⊗ C_{i})). (7.13)

We transform (7.13) by applying Lemma 123 again. In this case we must prove that, for each i = 1, …, n,

¯∃ ; ¯∃X̄ Ĉ^{s} = ¯∃ ; ¯∃X̄(¯∃ ; ¯∃X̄ Ĉ^{c} ⊗ d_{X̄Ȳ} ⊗ Cρ ⊗ ⨂_{i=1}^{n} (d_{Ȳ_{i}X̄_{i}} ⊗ ¯∃ ; ¯∃X̄_{i} C_{i})). (7.14)

Consider, for each i = 1, …, n, the expression

E_{i} = (¯∃ ; ¯∃X̄ Ĉ^{c} ⊗ d_{X̄Ȳ} ⊗ Cρ

⊗ ⨂_{j=1}^{i−1} (d_{Ȳ_{j} X̄_{j}} ⊗ ¯∃ ; ¯∃X̄_{j} C_{j}) ⊗ ⨂_{j=i}^{n} (d_{Ȳ_{j} X̄_{j}} ⊗ C_{j}))

The right-hand sides of (7.13) and (7.14) are given by E_{1} and E_{n+1}, respectively. It is possible to prove that E_{1} = E_{n+1} by showing that E_{1} = E_{2}, E_{2} = E_{3}, …, E_{n} = E_{n+1}. The equality E_{i} = E_{i+1} can be obtained by using Lemma 125. To this purpose, we consider the following expression for E_{i}:

E_{i} = \overbrace{(¯∃ ; ¯∃X̄ C ⊗ d_{X̄Ȳ} ⊗ Cρ ⊗ ⨂_{j=1}^{i−1} (d_{Ȳ_{j} X̄_{j}} ⊗ ¯∃ ; ¯∃X̄_{j} C_{j})}^{C^{i−1}}

⊗ d_{Ȳ_{i}X̄_{i}} ⊗ C_{i} ⊗ \underbrace{⨂_{j=i+1}^{n}(d_{Ȳ_{j} X̄_{j}} ⊗ C_{j})}_{C^{i+1}}) (7.15)

By ⊗ commutativity and associativity, we can write

E_{i} = C_{i} ⊗ d_{Ȳ_{i}X̄_{i}} ⊗ (C^{i−1} ⊗ C^{i+1}).  (7.16)

The hypotheses of Lemma 123 are checked, for each i = 1, …, n, as follows (still using point 3 of Definition 114):

FV (C_{i}) ∩ X̄ ⊆ ( ⋃_{j=k+h_{i}}^{k+h_{i+1}−1} FV (R_{j})) ∩ (⋃_{j=1}^{k} FV (R_{j}))

= ∅ ⊆ Ȳ_{i};

FV (C_{i}) ∩ FV (C^{i−1} ⊗ C^{i+1})

⊆ FV (C_{i}) ∩ (FV (C^{i−1}) ∪ FV (C^{i+1}))

= (FV (C_{i}) ∩ FV (C^{i−1})) ∪ (FV (C_{i}) ∩ FV (C^{i+1}))

⊆ (( ⋃_{j=k+h_{i}}^{k+h_{i+1}−1} FV (R_{j})) ∩ ( ⋃_{j=1}^{k+h_{i}−1} FV (R_{j})))

∪ (( ⋃_{j=k+h_{i}}^{k+h_{i+1}−1} FV (R_{j})) ∩ ( ⋃_{j=k+h_{i+1}}^{N} FV (R_{j})))

= ∅;

FV (C_{i}) ∩ X̄_{i} ⊆ ( ⋃_{j=k+h_{i}}^{k+h_{i+1}−1} FV (R_{j})) ∩ FV (R_{k})

= ∅.

Thus, by Lemma 123 we can write, for each i = 1, …, n,

E_{i} = ¯∃ ; ¯∃X̄_{i} C_{i} ⊗ d_{Ȳ_{i}X̄_{i}} ⊗ (C^{i−1} ⊗ C^{i+1}).

By substituting the expressions for C^{i−1} and C^{i+1} we have the thesis.

Theorem 127 (117) (Equivalence.) Let D̄ be a strong interpretation domain and D̄_{F} the functional representation over D̄. Given a CLP(X) program P̃ and a set G̃ of CLP(X) goals, a constraint interpretation [[·]]D̄ ; C, let P and G be the CLP(D̄) program and goals obtained from P̃ and G̃ through [[·]]D̄ ; C. Let also P_{m}^{G} be the CLP(D̄) program obtained from P̃, G̃, and [[·]]D̄ ; C as in Definition 116. Then

σ(OD̄_{F}(P, G)) = F D̄_{F} ; P_{G} ↾ {1, …, # P }.

Moreover, for each r ∈ {1, …, # P },

(C ↦ 0) ∈ OD̄_{F}(P, G)(r) ⟹

∃r′ ∈{ # P + 1, …, # P_{m}^{G}} . (C ↦ C) ∈ F D̄_{F} ; P_{G}(r′). (7.17)

Proof Since the proof, while not difficult, is quite complicated, we will adhere strictly to the following notational conventions. We will focus on the the r-th clause of the program, P [r]. It is given by

P [r] =^{def} (p(Ū) :− C □ p_{1}(Ū_{1}), …, p_{n}(Ū_{n})).

In the top-down derivations we will consider, in particular, a variant of P [r], obtained from P [r] through the application of a renaming ρ:

P [r]ρ =^{def} (p(Ȳ) :− Cρ □ p_{1}(Ȳ_{1}), …, p_{n}(Ȳ_{n})),

whence it is clear that Ūρ = Ȳ, Ū_{1}ρ = Ȳ_{1}, …, Ū_{n}ρ = Ȳ_{n}.

Staten that p_{i} always denotes the predicate symbol of the i-th atom goal in the body of P [r], we will also talk about n clauses in P defining p_{i}, for each i = 1, …, n. The indexes for these clauses are r_{1}, …, r_{n}, respectively. When, for some i = 1, …, n, we use a variant of r_{i} in a derivation, we will write it as

p_{i}(X̄_{i}) :− C_{i}ρ_{i} □ B_{i}.

So far for the original program P and the variants of its clauses, we now deal with the magic program P_{m}^{G}. Recall that our transformation has the following property: the modified clause corresponding to P [r] is P_{m}^{G}[r]; the magic clauses are in the range # P + 1, …, # P_{m}^{G}. Moreover, the magic clauses are normalized so that the variable symbols that appear in their heads are initial segments of Λ, denoted by Λ⃗. Thus, the modified clause P_{m}^{G}[r] is

p(Λ⃗) :− ⟨{m p(Λ⃗)}, Cρ̈, (p_{1}(W̄_{1}), …, p_{n}(W̄_{n}))⟩,

for some renaming ρ̈ such that

Ūρ̈ = Λ⃗, Ū_{1}ρ̈ = W̄_{1}, …, Ū_{n}ρ̈ = W̄_{n}.

In the magic program we will also have n magic clauses, one for each call in the body of P [r]. These magic clauses will have indexes r′_{1}, …, r′_{n}, and will define the predicates m p_{1}, …, m p_{n}, respectively. For each i = 1, …, n, the clause P_{m}^{G}[r′_{i}] is

m p_{i}(Λ⃗_{i}) :− ⟨{m p(V̄^{i})}, Cρ̈_{i}, (p_{1}(V̄_{1}^{i}), …, p_{i−1}(V̄_{i−1}^{i}))⟩,

where Ūρ̈_{i} = V̄^{i}, Ū_{1}ρ̈_{i} = V̄_{1}^{i}, …, Ū_{i−1}ρ̈_{i} = V̄_{i−1}^{i}, and Ū_{i}ρ̈_{i} = Λ⃗_{i}.

In the proof we will consider, among other things, a derivation such that:

1. it starts with a goal G_{0} drawn from the set G of initial goals;

2. after a certain number of derivation steps (possibly 0), it performs a step employing a variant of P [r];

3. it performs other derivation steps and stops as soon as one of the following condition holds:

• a goal with an unsatisfiable constraint store is reached;

• the last call in the body of P [r] “returns”.

Such a derivation will be written as follows:

\begin{aligned}G_{0} & ⇝_{P}^{∗} & Ĉ^{c} □ p(X̄) :: B \\ & ⇝_{\{r\}} & Ĉ_{1}^{c} □ (p_{1}(Ȳ_{1}), …, p_{n}(Ȳ_{n})):: B \\ & ⇝_{P}^{∗} & Ĉ_{1}^{s} □ (p_{2}(Ȳ_{2}), …, p_{n}(Ȳ_{n})):: B \\ & = & Ĉ_{2}^{c} □ (p_{2}(Ȳ_{2}), …, p_{n}(Ȳ_{n})):: B \\ & ⇝_{P}^{∗} & . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . \\ & ⋮ & ⋮ \\ & ⇝_{P}^{∗} & . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . \\ & ⇝_{P}^{∗} & Ĉ_{m}^{c} □ (p_{m}(Ȳ_{m}), …, p_{n}(Ȳ_{n})):: B \\ & ⇝_{P}^{∗} & Ĉ^{s} □ B′ :: B, \end{aligned}  (7.18)

with the condition that either Ĉ^{s} = 0 or Ĉ^{s} ≠ 0 and B′ = ϵ. We use this prototypical partial derivation in order to define the following symbols:

• the k-th goal in (7.18) is G_{k};

• the variant of clause used to rewrite G_{k} is denoted by R_{k}, with k ≥ 0 (see Definition 114 on page 286);

• Ĉ^{c} is the constraint store at the time of the considered call to P [r];

• if Ĉ^{s} ≠ 0 then Ĉ^{s} is the constraint store on exit to the above call;

• for each i = 1, …, n, if the call to p_{i} in the body of P [r] takes place in (7.18), then the constraint store at the time of the call is Ĉ_{i}^{c};

• for each i = 1, …, n, if the call mentioned above succeeds, then the constraint store on exit is Ĉ_{i}^{s}; in that case we have also Ĉ_{i}^{s} = Ĉ_{i+1}^{c} if i < n.

In order to ascertain that these hypothesis are clear, please make sure you understand that in (7.18) we have Ĉ_{1}^{c} = Ĉ^{c} ⊗ d_{X̄Ȳ} ⊗ Cρ.

Still on notation. Our convention is that, while descriptions arising from top-down (partial) derivation are characterized by a “hat accent”, as in Ĉ_{sub}^{sup}, the non-accented version C_{sub}^{sup} denotes the same description projected and renamed onto Λ⃗. For instance, in what follows we will use the (possibly subscripted) symbols Ĉ^{s} and C^{s} that are correlated by

C^{s} = (¯∃ ; ¯∃X̄ Ĉ^{s})[Λ⃗/X̄].

We will also use “reversed hats” to denote descriptions arising from the bottom-up computations, Č^{s} for instance. With this notation, one of the objectives of the proof will be to show, for example, that

(¯∃ ; ¯∃X̄ Ĉ^{s})[Λ⃗/X̄] = (¯∃ ; ¯∃Λ⃗ Č^{s}) = C^{s}.

We also assume, without loss of generality, that variables employed in top-down derivations are totally distinct from those used in the bottom-up construction.^{8}

All the properties indicated by H_{i}, for i = 1, …, 7 refer to Hypothesis 121 on page 299.

First Inclusion

We start by showing the inclusion

σ(OD̄_{F}(P, G)) ⊆ F D̄_{F} ; P_{G} ↾ {1, …, # P }

and the implication (7.17), for each r ∈ {1, …, # P }. We need to show that for each G_{0} ∈ G:

8This assumption is not restrictive considering that we work on expressions deriving from two distinct mechanisms (top-down and bottom-up). A way to avoid collisions is, for instance, to use variables with an even index for the top-down derivations, reserving those with an odd index for the bottom-up construction.

(a) if G_{0} = (Ĉ^{c} □ p(X̄) :: B), then there exists a clause r′ in P_{m}^{G}, describing the first call in G_{0}, such that

(C^{c} ↦ C^{c}) ∈ F D̄_{F} ; P_{G}(r′), where C^{c} =^{def} (¯∃ ; ¯∃X̄ Ĉ^{c})[Λ⃗/X̄].

Moreover, suppose there exists a derivation

G_{0} ⇝_{P}^{m} Ĉ^{c} □ p(X̄) :: B
⇝_{{R_{k}}} Ĉ^{c} ⊗ d_{X̄Ȳ} ⊗ Cρ □ (p_{1}(Ȳ_{1}), …, p_{n}(Ȳ_{n})):: B
⇝_{P}^{h} Ĉ_{i} □ (p_{i}(Ȳ_{i}), …, p_{n}(Ȳ_{n})):: B,

where m, h ∈ ℕ, R_{k} = (p(Ȳ) :− Cρ □ p_{1}(Ȳ_{1}), …, p_{n}(Ȳ_{n})) is a variant of P [r] such that

FV (R_{k}) ∩ (FV (G_{0}) ∪ ( ⋃_{j=1}^{k−1} FV (R_{j}))) = ∅.  (7.19)

Then there exists a magic clause ri^{′}, obtained from P [r] (i.e., describing the i-th call in the body of P [r]), such that

(C_{i}^{c} ↦ C_{i}^{c}) ∈ F D̄_{F} ; P_{G}(r′_{i}),

where C_{i}^{c} =^{def} (¯∃ ; ¯∃Ȳ_{i} Ĉ_{i}^{c})[Λ⃗_{i}/Ȳ_{i}].

(b) Suppose there exists a derivation

G_{0} ⇝_{P}^{m} Ĉ^{c} □ p(X̄) :: B

⇝_{{R_{k}}} Ĉ^{c} ⊗ d_{X̄Ȳ} ⊗ Cρ □ (p_{1}(X̄_{1}), …, p_{n}(X̄_{n})):: B

⇝_{P}^{l} Ĉ^{s} □ B′ :: B,

where m, l ∈ ℕ and R_{k} = (p(Ȳ) :− Cρ □ p_{1}(Ȳ_{1}), …, p_{n}(Ȳ_{n})) is a variant of P [r] renamed apart as in (7.19). Then, if Ĉ^{s} ≠ 0 and B′ = ϵ, we have

(C^{c} ↦ C^{s}) ∈ F D̄_{F} ; P_{G}(r),

where

C^{c} =^{def} (¯∃ ; ¯∃X̄ Ĉ^{c})[Λ⃗/X̄])

and

C^{s} =^{def} (¯∃ ; ¯∃X̄ Ĉ^{s})[Λ⃗/X̄]).

Otherwise, if Ĉ^{s} = 0 then

∃r′ ∈{ # P + 1, …, # P_{m}^{G}} . (C^{c} ↦ C^{c}) ∈ F D̄_{F} ; P_{G}(r′).

The proof is by total induction on the length N of the considered derivations.

Base case (a). Let N = 0. The derivation coincides with the initial query G_{0} = (Ĉ^{c} □ p(X̄) :: B). By definition of magic program, the first call in G_{0} corresponds to the normalized clause P_{m}^{G}[r′] = (m p(Λ⃗) :− Ĉ^{c}[Λ⃗/X̄]). So, by η_{r′} definition, (C^{c} ↦ C^{c}) belongs to (T D̄_{F} ; P_{G} ↑1)(r′) and thus to F D̄_{F} ; P_{G}(r′).

Base case (b). Let N = 1 (a success requires at least one derivation step). Consider the partial derivation

G_{0} = (Ĉ^{c} □ p(X̄) :: B) ⇝_{\{R_{1}\}} \overbrace{Ĉ^{c} ⊗ d_{X̄Ȳ} ⊗ Cρ}^{Ĉ^{s}} □ B  (7.20)

where R_{1} = (p(Ȳ) :− Cρ □ ∅) is a variant of P [r] renamed apart from G_{0}. The renaming ρ is such that Ūρ = Ȳ. By the base case (a), there is a clause r′ in P_{m}^{G}, corresponding to the first call in G_{0}, such that

(C^{c} ↦ C^{c}) ∈ F D̄_{F} ; P_{G}(r′).  (7.21)

Let P_{m}^{G}[r] = (p(Λ⃗) :− ⟨{m p(Λ⃗)}, Cρ̈, ∅⟩), where Ūρ̈ = Λ⃗. Let Ĉ^{s} = Ĉ^{c} ⊗ d_{X̄Ȳ} ⊗ Cρ the partial answer constraint in (7.20).

If Ĉ^{s} = 0 then (C^{c} ↦ 0) ∈ OD̄_{F}(P, G)(r), and, by (7.21), the thesis (7.17) holds.

If Ĉ^{s} ≠ 0 we reason as follows. Consider the function T D̄_{F} ; P_{G} as given in Definition 116 on page 290. In the computation of

T D̄_{F} ; P_{G}(r, F D̄_{F} ; P_{G}) we have

S′ = ϖ_{r}(F D̄_{F} ; P_{G}(r′), Λ⃗, Λ⃗, Z̄),  (7.22)

where Z̄ ≪Λ⃗ FV (P_{m}^{G}[r]) ∪ Λ, and thus

S = S′ ⊗_{F} Cρ̈.

By manipulating (7.22), taking into account (7.21), and considering that P_{m}^{G}[r] is a modified clause, we obtain (C^{c} ↦ C^{c}) ∈ S′.

Observe that T D̄_{F} ; P_{G}(F D̄_{F} ; P_{G}) = F D̄_{F} ; P_{G}, since F D̄_{F} ; P_{G} is a fixpoint; there-

fore ¯∃ ; ¯∃Λ⃗ η_{r}(S) = ¯∃ ; ¯∃Λ⃗ S ⊆ F D̄_{F} ; P_{G}(r). By ⊗_{F} distributivity with respect to ∪ we have that

(C^{c} ↦ ¯∃ ; ¯∃Λ⃗(\underbrace{C^{c} ⊗ Cρ̈})) ∈ F D̄_{F} ; P_{G}(r)  (7.23)

provided that, by ⊗_{F} definition,

C^{c} ⊗ Cρ̈ ≠ 0.  (7.24)

In other words, for (7.23) to hold, we must ensure that the indicated pair is not “discarded” by the ⊗_{F} operator, i.e., (7.24) holds. We will now show that

¯∃ ; ¯∃Λ⃗(C^{c} ⊗ Cρ̈) = (¯∃ ; ¯∃X̄(\underbrace{Ĉ^{c} ⊗ d_{X̄Ȳ} ⊗ Cρ}_{Ĉ^{s}}))[Λ⃗/X̄].  (7.25)

This way, since Ĉ^{s} ≠ 0, we will also prove that (7.24) and (7.23) hold. The validity of (7.25) is proved as follows:

(¯∃ ; ¯∃X̄(Ĉ^{c} ⊗ d_{X̄Ȳ} ⊗ Cρ))[Λ⃗/X̄]

= (¯∃ ; ¯∃X̄(¯∃ ; ¯∃X̄ Ĉ^{c} ⊗ d_{X̄Ȳ} ⊗ Cρ))[Λ⃗/X̄]

[by Lemma 126]

= ¯∃ ; ¯∃Λ⃗((¯∃ ; ¯∃X̄ Ĉ^{c} ⊗ d_{X̄Ȳ} ⊗ Cρ)[Λ⃗/X̄])

[by H_{4}]

= ¯∃ ; ¯∃Λ⃗((¯∃ ; ¯∃X̄ Ĉ^{c})[Λ⃗/X̄] ⊗ d_{Λ⃗Ȳ} ⊗ Cρ[Λ⃗/X̄])

[by H_{3}]

= ¯∃ ; ¯∃Λ⃗(¯∃ ; ¯∃Λ⃗(Ĉ^{c}[Λ⃗/X̄])⊗d_{Λ⃗Ȳ} ⊗ Cρ)

[by H_{4} and since FV (Cρ) ∩ X̄ ⊆ FV (R_{1}) ∩ FV (G_{0}) = ∅]

= ¯∃ ; ¯∃Λ⃗(Ĉ^{c}[Λ⃗/X̄])⊗ ¯∃ ; ¯∃Λ⃗(d_{Λ⃗Ȳ} ⊗ Cρ)

[by S_{4}]

= ¯∃ ; ¯∃Λ⃗(Ĉ^{c}[Λ⃗/X̄])⊗ ¯∃ ; ¯∃Λ⃗(Cρ[Λ⃗/Ȳ])

[by S_{5}, since Ȳ (ρ[Λ⃗/Ȳ]) = Λ⃗]

= ¯∃ ; ¯∃Λ⃗(¯∃ ; ¯∃Λ⃗(Ĉ^{c}[Λ⃗/X̄])⊗Cρ̈)

[by S_{4} and since ρ̈ = ρ[Λ⃗/Ȳ]]

= ¯∃ ; ¯∃Λ⃗(C^{c} ⊗ Cρ̈)

[by def. of C^{c}]

Thus the thesis, i.e., (C^{c} ↦ C^{s}) ∈ F D̄_{F} ; P_{G}(r), follows by (7.25).

Inductive case (b). Assume the thesis is valid for derivations whose length is less than N. We prove that it holds also for derivations of length N (case (b)). Consider a partial derivation

\begin{aligned}G_{0} & ⇝_{P}^{m} Ĉ^{c} □ p(X̄) :: B \\ ⇝_{\{R_{k}\}} & \underbrace{Ĉ^{c} ⊗ d_{X̄Ȳ} ⊗ Cρ □ (p_{1}(Ȳ_{1}), …, p_{n}(Ȳ_{n})):: B}_{G′} \\ & ⇝_{P}^{l} Ĉ^{s} □ B′ :: B, \end{aligned} (7.26)

where m + l + 1 = N and

R_{k} = (p(Ȳ) :− Cρ □ (p_{1}(Ȳ_{1}), …, p_{n}(Ȳ_{n})))

is a variant of P [r] such that

FV (R_{k}) ∩ (FV (G_{0}) ∪ ⋃_{j=1}^{k−1} FV (R_{j})) = ∅

and Ūρ = Ȳ, Ū_{1}ρ = Ȳ_{1}, …, Ū_{n}ρ = Ȳ_{n}. This derivation is responsible for the fact that

(C^{c} ↦ C^{s}) ∈ OD̄_{F}(P, G)(r).  (7.27)

If Ĉ^{s} = 0 then (C^{c} ↦ 0) ∈ OD̄_{F}(P, G)(r). This holds because there is a derivation whose length is either

• zero, in which case we must have G_{0} = (Ĉ^{c} □ p(X̄) :: B). By the inductive hypothesis there exists r′ such that P_{m}^{G}[r′] = (m p(Λ⃗) :− Ĉ^{c}[Λ⃗/X̄]) and

(C^{c} ↦ C^{c}) ∈ F D̄_{F} ; P_{G}(r′).  (7.28)

Or the length of the derivation is • greater than zero. This derivation is of the form

G_{0} ⇝_{P}^{m_{1}} C′ □ q(W̄) :: B_{0}
⇝_{{R_{p}}} C̃ □ B_{1} :: p(X̄) :: B_{2} :: B_{0}
⇝_{P}^{m_{2}} Ĉ^{c} □ p(X̄) :: B_{2} :: B_{0}

where m_{1} +m_{2} +1 = m < N, R_{p} is a variant of some program clause P [r′′]. P_{m}^{G}[r′] is the magic clause describing the j-th call to p in the body of r′′, where j = # B_{1} + 1. By the inductive hypothesis (7.28) holds in this case too.

Suppose, instead, that Ĉ^{s} ≠ 0. By Definition 115 we have that, in (7.26), B′ = ϵ and Ĉ^{s} is the constraint store at the successful exit from the indicated call to P [r]. We will now rewrite Ĉ^{s}, in order to show that the pair in (7.27) belongs also to F D̄_{F} ; P_{G}(r). A consequence of (7.26) is that, for each i = 1, …, n, there must exist derivations

\begin{aligned}1 □ p_{i}(Ȳ_{i}) & ⇝_{\{R_{k+h_{i}}\}} 1 ⊗ d_{Ȳ_{i}X̄_{i}} ⊗ C_{i}ρ_{i} □ B_{r_{i}} \\ & ⇝_{P}^{k_{i}} d_{Ȳ_{i}X̄_{i}} ⊗ C̀_{i}^{s} □ ∅ \end{aligned}  (7.29)

such that^{9} k_{i} < l, h_{i} > 0 and R_{k+h_{i}} = (p_{i}(X̄_{i}) :− C_{i}ρ_{i} □ B_{r_{i}}) is a variant of P [r_{i}] with

FV (R_{k+h_{i}}) ∩ (FV (G_{0}) ∪ ⋃_{j=1}^{k+h_{i}−1} FV (R_{j})) = ∅.  (7.30)

Observe that the variants used in the derivation (7.26) up to the call of p_{i} in G′, for each i = 1, …, n, are R_{1}, …, R_{k+h_{i}−1}. Obviously, h_{i} < h_{i+1}. Observe also that in (7.29) we have used the same variants employed in (7.26). This implies that FV (C̀_{i}^{s}) ∩ Ȳ_{i} = ∅. Thus we can write

Ĉ^{s} = Ĉ^{c} ⊗ d_{X̄Ȳ} ⊗ Cρ ⊗ ⨂_{i=1}^{n} (d_{Ȳ_{i}X̄_{i}} ⊗ C̀_{i}^{s}).  (7.31)

9Notice that ∑_{i=1}^{n}(k_{i} + 1) = l.

We have thus obtained our final formulation for the partial answer constraint of the partial derivation (7.20):

(¯∃ ; ¯∃X̄ Ĉ^{s})[Λ⃗/X̄] = ¯∃ ; ¯∃X̄(Ĉ^{c} ⊗ d_{X̄Ȳ} ⊗ Cρ ⊗ ⨂_{i=1}^{n} (d_{Ȳ_{i}X̄_{i}} ⊗ C̀_{i}^{s}))[Λ⃗/X̄]

= ¯∃ ; ¯∃X̄(¯∃ ; ¯∃X̄ Ĉ^{c} ⊗ d_{X̄Ȳ} ⊗ Cρ

⊗ ⨂_{i=1}^{n} (d_{Ȳ_{i}X̄_{i}} ⊗ ¯∃ ; ¯∃X̄_{i} C̀_{i}^{s}))[Λ⃗/X̄]

[by Lemma 126]

(7.32)

We will now show that the bottom-up construction derives, for P [r], a pair that, taking into account (7.32), is identical to the pair in (7.27).

Recall that the partial derivation (7.26) employs the following variant of P [r]:

R_{k} = (p(Ȳ) :− Cρ □ (p_{1}(Ȳ_{1}), …, p_{n}(Ȳ_{n}))).

By definition of magic program, there exists a renaming ρ̈ with Ūρ̈ = Λ⃗, Ū_{1}ρ̈ = W̄_{1}, …, Ū_{n}ρ̈ = W̄_{n}, such that P_{m}^{G} contains the following clauses derived from P [r]:

P_{m}^{G}[r] = (p(Λ⃗) :− ⟨{m p(Λ⃗)}, Cρ̈, (p_{1}(W̄_{1}), …, p_{n}(W̄_{n}))⟩), (7.33)

and, for each i = 1, …, n,

P_{m}^{G}[r′_{i}] = (m p_{i}(Λ⃗_{i}) :− ⟨{m p(V̄^{i})}, Cρ̈, (p_{1}(V̄_{1}^{i}), …, p_{i−1}(V̄_{i−1}^{i}))⟩). (7.34)

By the inductive hypothesis we have that:

• (C^{c} ↦ C^{c}) ∈ F D̄_{F} ; P_{G}(r′) (as stated in (7.28));

• for each i = 1, …, n, by (7.26) we have that

G_{0} ⇝_{P}^{m} Ĉ^{c} □ p(X̄) :: B
⇝_{{R_{k}}} Ĉ^{c} ⊗ d_{X̄Ȳ} ⊗ Cρ □ (p_{1}(Ȳ_{1}), …, p_{n}(Ȳ_{n})):: B
⇝_{P}^{h} Ĉ_{i}^{c} □ (p_{i}(Ȳ_{i}), …, p_{n}(Ȳ_{n})):: B

is a derivation whose length is less than N, where

Ĉ_{i}^{c} = Ĉ^{c} ⊗ d_{X̄Ȳ} ⊗ Cρ ⊗ ⨂_{j=1}^{i−1} (d_{Ȳ_{j} X̄_{j}} ⊗ C̀_{j}^{s}),

each C̀_{j}^{s} being obtained from the derivations in (7.29). Thus, there exists a clause in P_{m}^{G} (precisely, clause r′_{i} as given in (7.34)) such that (C_{i}^{c} ↦ C_{i}^{c}) ∈ F D̄_{F} ; P_{G}(r′_{i}).

• For each i = 1, …, n, by (7.26) we have that

G_{0} ⇝_{P}^{m} Ĉ^{c} □ p(X̄) :: B

⇝_{P}^{h+1} Ĉ_{i}^{c} □ (p_{i}(Ȳ_{i}), …, p_{n}(Ȳ_{n})):: B ⇝_{{R_{k+h_{i}}}} Ĉ_{i}^{c} ⊗ d_{Ȳ_{i}X̄_{i}} ⊗ C_{i}ρ_{i}

□ B_{i} :: (p_{i+1}(Ȳ_{i+1}), …, p_{n}(Ȳ_{n})):: B

⇝_{P}^{k_{i}} Ĉ_{i}^{s} □ (p_{i+1}(Ȳ_{i+1}), …, p_{n}(Ȳ_{n})):: B is a partial derivation of length less than N, where R_{k+h_{i}} is a variant of P [r_{i}] (satisfying the conditions described in (7.30)), Ĉ_{i}^{s} ≠ 0 (since Ĉ^{s} ≠ 0) and

Ĉ_{i}^{s} = Ĉ^{c} ⊗ d_{X̄Ȳ} ⊗ Cρ ⊗ ⨂_{j=1}^{i} (d_{Ȳ_{j} X̄_{j}} ⊗ C̀_{j}^{s}).  (7.35)

7.6. Proof of the Main Result  321

Thus, (C_{i}^{c} ↦ C_{i}^{s}) ∈ F D̄_{F} ; P_{G}(r_{i}).

By Lemma 126 we have that, for each i = 1, …, n:

¯∃ ; ¯∃X̄_{i} Ĉ_{i}^{s} = ¯∃ ; ¯∃X̄_{i}(¯∃ ; ¯∃X̄ Ĉ^{c} ⊗ d_{X̄Ȳ} ⊗ Cρ ⊗ ⨂_{j=1}^{i} (d_{Ȳ_{j} X̄_{j}} ⊗ ¯∃ ; ¯∃X̄_{j} C̀_{j}^{s})), (7.36)

and thus

(¯∃ ; ¯∃X̄_{i} Ĉ_{i}^{s})[Λ⃗_{i}/X̄_{i}] =

(¯∃ ; ¯∃X̄_{i}(¯∃ ; ¯∃X̄ Ĉ^{c} ⊗ d_{X̄Ȳ} ⊗ Cρ ⊗ ⨂_{j=1}^{i} (d_{Ȳ_{j} X̄_{j}} ⊗ ¯∃ ; ¯∃X̄_{j} C̀_{j}^{s})))[Λ⃗_{i}/X̄_{i}]. (7.37)

With reference to Definition 116, the evaluation of T D̄_{F} ; P_{G}(r, F D̄_{F} ; P_{G}) gives:

S′ = ϖ_{r}(F D̄_{F} ; P_{G}(r′), Λ⃗, Λ⃗, Z̄),  (7.38)

and, for each i = 1, …, n,

S_{i} = (F D̄_{F} ; P_{G}(r_{i}))[Z̄_{i}/Λ⃗_{i}]  (7.39)

where

Z̄ ≪Λ⃗ FV (P_{m}^{G}[r]) ∪ Λ,  (7.40)

and

Z̄_{i} ≪Λ⃗_{i} FV (P_{m}^{G}[r]) ∪ Λ ∪ Z̄ ∪ Z̄_{1} ∪ ⋯ ∪ Z̄_{i−1}.  (7.41)

Thus

S = S′ ⊗_{F} Cρ̈ ⊗_{F} \begin{matrix}n \\ ⨂ & _{F} \\ i=1 \end{matrix}(d_{W̄_{i}Z̄_{i}} ⊗_{F} S_{i})  (7.42)

Again, by manipulating (7.38), taking into account (7.28) and considering that P_{m}^{G}[r] is a modified clause, we obtain (C^{c} ↦ C^{c}) ∈ S′; moreover, for each i = 1, …, n, (C_{i}^{c} ↦ C_{i}^{s}) ∈ S_{i}.

We also have ¯∃ ; ¯∃Λ⃗ η_{r}(S) = ¯∃ ; ¯∃Λ⃗ S ⊆ F D̄_{F} ; P_{G}(r), since T D̄_{F} ; P_{G}(F D̄_{F} ; P_{G}) =

F D̄_{F} ; P_{G}. From ⊗_{F} distributivity with respect to ∪ and from (7.37) we obtain

(C^{c} ↦ ¯∃ ; ¯∃Λ⃗ Č^{s}) ∈ F D̄_{F} ; P_{G}(r),  (7.43)

where the success component ¯∃ ; ¯∃Λ⃗ Č^{s} can be written as follows:

¯∃ ; ¯∃Λ⃗ Č^{s} =^{def} ¯∃ ; ¯∃Λ⃗(C^{c} ⊗ Cρ̈ ⊗ ⨂_{i=1}^{n} (d_{W̄_{i}Z̄_{i}} ⊗ (¯∃ ; ¯∃Λ⃗_{i} Č_{i}^{s})[Z̄_{i}/Λ⃗_{i}])), (7.44)

provided that, by ⊗_{F} definition, Č^{s} ≠ 0 and, considering that, for each i = 1, …, n, it is

FV ((¯∃ ; ¯∃Λ⃗ Č_{i}^{c})[Z̄_{i}/Λ⃗_{i}]) ⊆ Z̄_{i},

we have

¯∃ ; ¯∃Z̄_{i}(\overbrace{C^{c} ⊗ Cρ̈ ⊗ ⨂_{j=1}^{i−1} (d_{W̄_{j} Z̄_{j}} ⊗ (¯∃ ; ¯∃Λ⃗_{j} Č_{j}^{s})[Z̄_{j}/Λ⃗_{j}]) ⊗ d_{W̄_{i}Z̄_{i}}}^{C^{i}})

= (¯∃ ; ¯∃Λ⃗ Č_{i}^{c})[Z̄_{i}/Λ⃗_{i}]. (7.45)

Now we prove that (7.45) holds. First we note that

(¯∃ ; ¯∃Λ⃗ Č_{i}^{c})[Z̄_{i}/Λ⃗_{i}] = (¯∃ ; ¯∃Ȳ_{i} Ĉ_{i}^{c})[Z̄_{i}/Ȳ_{i}]  (7.46)

[by the inductive hypothesis and H_{5}]

= (¯∃ ; ¯∃Ȳ_{i}(¯∃ ; ¯∃X̄ Ĉ^{c} ⊗ d_{X̄Ȳ} ⊗ Cρ

⊗ ⨂_{j=1}^{i−1} (d_{Ȳ_{j} X̄_{j}} ⊗ ¯∃ ; ¯∃X̄_{j} C̀_{i}^{s})))[Z̄_{i}/Ȳ_{i}]

[derives from (7.36)]

= ¯∃ ; ¯∃Z̄_{i}((¯∃ ; ¯∃X̄ Ĉ^{c} ⊗ d_{X̄Ȳ} ⊗ Cρ

⊗ ⨂_{j=1}^{i−1} (d_{Ȳ_{j} X̄_{j}} ⊗ ¯∃ ; ¯∃X̄_{j} C̀_{i}^{s}))[Z̄_{i}/Ȳ_{i}]) (7.47)

[by H_{4}]

Moreover,

(¯∃ ; ¯∃Λ⃗_{i} Č_{i}^{s})[Z̄_{i}/Λ⃗_{i}] = (¯∃ ; ¯∃X̄_{i} Ĉ_{i}^{s})[Λ⃗_{i}/X̄_{i}][Z̄_{i}/Λ⃗_{i}]

= (¯∃ ; ¯∃X̄_{i}(Ĉ_{i}^{c} ⊗ d_{Ȳ_{i}X̄_{i}} ⊗ ¯∃ ; ¯∃X̄_{i} C̀_{i}^{s}))[Z̄_{i}/X̄_{i}]

[by (7.29) and (7.36)]

= (¯∃ ; ¯∃X̄_{i}(Ĉ_{i}^{c} ⊗ d_{Ȳ_{i}X̄_{i}}) ⊗ ¯∃ ; ¯∃X̄_{i} C̀_{i}^{s})[Z̄_{i}/X̄_{i}]

[by axiom S_{4} of Definition 110]

= (¯∃ ; ¯∃X̄_{i}(Ĉ_{i}^{c}[X̄_{i}/Ȳ_{i}]) ⊗ ¯∃ ; ¯∃X̄_{i} C̀_{i}^{s})[Z̄_{i}/X̄_{i}]

[by S_{5}, since X̄_{i} ∩ Ȳ_{i} = ∅]

= (¯∃ ; ¯∃X̄_{i}(Ĉ_{i}^{c}[X̄_{i}/Ȳ_{i}]))[Z̄_{i}/X̄_{i}] ⊗ (¯∃ ; ¯∃X̄_{i} C̀_{i}^{s})[Z̄_{i}/X̄_{i}]

[by H_{3}]

= (¯∃ ; ¯∃Ȳ_{i} Ĉ_{i}^{c})[X̄_{i}/Ȳ_{i}][Z̄_{i}/X̄_{i}] ⊗ (¯∃ ; ¯∃X̄_{i} C̀_{i}^{s})[Z̄_{i}/X̄_{i}]

[by H_{4}, since FV (Ĉ_{i}^{c}) ∩ X̄_{i} = ∅]

= (¯∃ ; ¯∃Ȳ_{i} Ĉ_{i}^{c})[Z̄_{i}/Ȳ_{i}] ⊗ (¯∃ ; ¯∃X̄_{i} C̀_{i}^{s})[Z̄_{i}/X̄_{i}]  (7.48)

[by H_{5}, since Z̄_{i} ∩ Ȳ_{i} = ∅ and FV (Ĉ_{i}^{c}) ∩ Z̄_{i} = ∅]

Substituting (7.48) for (¯∃ ; ¯∃Λ⃗_{i} Č_{i}^{s})[Z̄_{i}/Λ⃗_{i}] in the over-braced expres-

7.6. Proof of the Main Result  325

sion in (7.45), we obtain, for i = 1, …, n:

¯∃ ; ¯∃Z̄_{i} C^{i}

= ¯∃ ; ¯∃Z̄_{i}(C^{c} ⊗ Cρ̈

⊗ ⨂_{j=1}^{i−1} (d_{W̄_{j} Z̄_{j}} ⊗ (¯∃ ; ¯∃Ȳ_{j} Ĉ_{j}^{c})[Z̄_{j}/Ȳ_{j}] ⊗ (¯∃ ; ¯∃X̄_{j} C̀_{j}^{s})[Z̄_{j}/X̄_{j}]) ⊗ d_{W̄_{i}Z̄_{i}})

= ¯∃ ; ¯∃Z̄_{i}((C^{c} ⊗ Cρ̈

⊗ ⨂_{j=1}^{i−1} (d_{W̄_{j} Z̄_{j}} ⊗ (¯∃ ; ¯∃Z̄_{j} Ĉ_{j}^{c})[Z̄_{j}/Ȳ_{j}] ⊗ (¯∃ ; ¯∃X̄_{j} C̀_{j}^{s})[Z̄_{j}/X̄_{j}]))[Z̄_{i}/W̄_{i}])

[by S_{5} with [Z̄_{i}/W̄_{i}] = ρ]

= ¯∃ ; ¯∃Z̄_{i}(((¯∃ ; ¯∃X̄ C^{c})[Λ⃗/X̄] ⊗ Cρ̈

⊗ ⨂_{j=1}^{i−1} (d_{W̄_{j} Z̄_{i}} ⊗ (¯∃ ; ¯∃Ȳ_{j} Ĉ_{j}^{c})[Z̄_{j}/Ȳ_{j}] ⊗ (¯∃ ; ¯∃X̄_{j} C̀_{j}^{s})[Z̄_{j}/X̄_{j}]))[Z̄_{i}/W̄_{i}])

[by definition of C^{c}]

= ¯∃ ; ¯∃Z̄_{i}((¯∃ ; ¯∃X̄ C^{c} ⊗ d_{X̄Λ⃗}

⊗ \overbrace{Cρ̈ ⊗ ⨂_{j=1}^{i−1} (d_{W̄_{j} Z̄_{j}} ⊗ (¯∃ ; ¯∃Ȳ_{j} Ĉ_{j}^{c})[Z̄_{j}/Ȳ_{j}] ⊗ (¯∃ ; ¯∃X̄_{j} C̀_{j}^{s})[Z̄_{j}/X̄_{j}])}^{F}
)[Z̄_{i}/W̄_{i}])

[by Lemma 124 since FV (F) ∩ X̄ = FV (Ĉ^{c}) ∩ Λ⃗ = X̄ ∩ Λ⃗ = Z̄_{i} ∩ X̄_{i} = ∅ ]

(7.49)

Now, we consider the renaming

ρ_{i−1} =^{def} [Ȳ /Λ⃗] ; [Ȳ_{1}/W̄_{1}] ; ⋯; [Ȳ_{n}/W̄_{n}] ; [X̄_{1}/Z̄_{1}] ; ⋯; [X̄_{i−1}/Z̄_{i−1}].

By (7.49) we can write

¯∃ ; ¯∃Z̄_{i} C^{i} = ¯∃ ; ¯∃Z̄_{i}(((¯∃ ; ¯∃X̄ C^{c} ⊗ d_{X̄Λ⃗} ⊗ Cρ̈

⊗ ⨂_{j=1}^{i−1} (d_{W̄_{j} Z̄_{j}} ⊗ (¯∃ ; ¯∃Ȳ_{j} Ĉ_{j}^{c})[Z̄_{j}/Ȳ_{j}]

⊗ (¯∃ ; ¯∃X̄_{j} C̀_{j}^{s})[Z̄_{j}/X̄_{j}]))[Z̄_{i}/W̄_{i}])ρ_{i−1})

\left[{by H_{6}, since FV (C^{i}) ∩ cod(ρ_{i−1}) = Z̄_{i} ∩ cod(ρ_{i−1}) = \atop Z̄_{i} ∩ dom(ρ_{i−1}) = ∅}\right]

= ¯∃ ; ¯∃Z̄_{i}((¯∃ ; ¯∃X̄ C^{c} ⊗ d_{X̄Ȳ} ⊗ Cρ̈ρ_{i−1}

⊗ ⨂_{j=1}^{i−1} (d_{Ȳ_{j} X̄_{j}} ⊗ (¯∃ ; ¯∃Ȳ_{j} Ĉ_{j}^{c})[X̄_{j}/Ȳ_{j}] ⊗ ¯∃ ; ¯∃X̄_{j} C̀_{j}^{s}))[Z̄_{i}/Ȳ_{i}])

[by H_{3} and then applying ρ_{i−1}]

= ¯∃ ; ¯∃Z̄_{i}((¯∃ ; ¯∃X̄ C^{c} ⊗ d_{X̄Ȳ} ⊗ Cρ

⊗ ⨂_{j=1}^{i−1} (d_{Ȳ_{j} X̄_{j}} ⊗ (¯∃ ; ¯∃Ȳ_{j} Ĉ_{j}^{c})[X̄_{j}/Ȳ_{j}] ⊗ ¯∃ ; ¯∃X̄_{j} C̀_{j}^{s}))[Z̄_{i}/Ȳ_{i}])

\left[{since ρ̈ρ_{i−1} = ρ[X̄_{1}/Z̄_{1}] ⋯ [X̄_{i−1}/Z̄_{i−1}] and \atop FV (C) ∩ Z̄_{k} = ∅, for each k = 1, …, i − 1}\right]

(7.50)

We will now show that

¯∃ ; ¯∃Z̄_{i} C^{i} = ¯∃ ; ¯∃Z̄_{i}((¯∃ ; ¯∃X̄ Ĉ^{c} ⊗ d_{X̄Ȳ} ⊗ Cρ

⊗ ⨂_{j=1}^{i−1} (d_{Ȳ_{j} X̄_{j}} ⊗ ¯∃ ; ¯∃X̄_{j} C̀_{j}^{s}))[Z̄_{i}/Ȳ_{i}]), (7.51)

where the right-hand side of (7.51) is the same as (7.47). This is done by considering the generic expression

E_{k} =^{def} ¯∃ ; ¯∃Z̄_{i}((¯∃ ; ¯∃X̄ Ĉ^{c} ⊗ d_{X̄Ȳ} ⊗ Cρ ⊗ ⨂_{j=1}^{k−1}(d_{Ȳ_{j} X̄_{j}} ⊗ ¯∃ ; ¯∃X̄_{j} C̀_{j}^{s})

⊗ ⨂_{j=k}^{i−1} (d_{W̄_{j} Z̄_{j}} ⊗ (¯∃ ; ¯∃Ȳ_{j} Ĉ_{j}^{c})[Z̄_{j}/Ȳ_{j}] ⊗ ¯∃ ; ¯∃X̄_{j} C̀_{j}^{s}))[Z̄_{i}/Ȳ_{i}]). (7.52)

The right-hand sides of (7.50) and (7.51) are given by E_{1} and E_{i}, respectively. It is possible to prove that E_{1} = E_{i} by showing that E_{1} = E_{2}, E_{2} = E_{3}, …, E_{i−1} = E_{i}. The equality E_{k} = E_{k+1} can be obtained using Lemma 125. To this purpose, we consider the following expression for E_{k}

E_{k} =^{def} ¯∃ ; ¯∃Z̄_{k}((\overbrace{¯∃ ; ¯∃X̄ Ĉ^{c} ⊗ d_{X̄Ȳ} ⊗ Cρ ⊗ ⨂_{j=1}^{k−1}(d_{Ȳ_{j} X̄_{j}} ⊗ ¯∃ ; ¯∃X̄_{j} C̀_{j}^{s})}^{D}

⊗ d_{Ȳ_{k}X̄_{k}} ⊗ (¯∃ ; ¯∃Ȳ_{k} Ĉ_{k}^{c})[Z̄_{k}/Ȳ_{k}] ⊗ (¯∃ ; ¯∃X̄_{k} C̀_{k}^{s})[Z̄_{k}/X̄_{k}]

⊗ ⨂_{j=k+1}^{i−1}(d_{Ȳ_{j} X̄_{j}} ⊗ (¯∃ ; ¯∃Ȳ_{j} Ĉ_{j}^{c})[Z̄_{j}/Ȳ_{j}] ⊗ (¯∃ ; ¯∃X̄_{j} C̀_{j}^{s})[Z̄_{j}/X̄_{j}]))[Z̄_{i}/Ȳ_{i}]), (7.53)

By (7.46) and (7.47) we have that, for each k = 1, …, i,

(¯∃ ; ¯∃Ȳ_{k} Ĉ_{i}^{c})[Z̄_{k}/Ȳ_{k}] = (¯∃ ; ¯∃Ȳ_{k} D)[Z̄_{k}/Ȳ_{k}],

thus Lemma 125 applies giving the desired result.

We will now show that

¯∃ ; ¯∃Λ⃗ Č^{s} = ¯∃ ; ¯∃Λ⃗(C^{c} ⊗ Cρ̈ ⊗ ⨂_{i=1}^{n} (d_{W̄_{i}Z̄_{i}} ⊗ (¯∃ ; ¯∃X̄_{i} C̀_{i}^{s})[Z̄_{i}/X̄_{i}])). (7.54)

This is done by considering that, by the inductive hypothesis, we can write:

(¯∃ ; ¯∃Λ⃗_{i} Č_{i}^{s}))[Z̄_{i}/Λ⃗_{i}] = (¯∃ ; ¯∃Ȳ_{i} Ĉ_{i}^{c})[Z̄_{i}/Ȳ_{i}] ⊗ (¯∃ ; ¯∃X̄_{i} C̀_{i}^{s})[Z̄_{i}/X̄_{i}]

[by (7.48)]

= (¯∃ ; ¯∃Λ⃗_{i} Č_{i}^{c})[Z̄_{i}/Λ⃗_{i}] ⊗ (¯∃ ; ¯∃X̄_{i} C̀_{i}^{s})[Z̄_{i}/X̄_{i}]

[by inductive hypothesis]

(7.55)

Now, we consider the generic expression

E_{i} =^{def} ¯∃ ; ¯∃Λ⃗(C^{c} ⊗ Cρ̈ ⊗ ⨂_{j=1}^{i−1} (d_{W̄_{j} Z̄_{j}} ⊗ (¯∃ ; ¯∃X̄_{j} C̀_{j}^{s})[Z̄_{j}/X̄_{j}])

⊗ ⨂_{j=i}^{n} (d_{W̄_{j} Z̄_{j}} ⊗ (¯∃ ; ¯∃Λ⃗_{j} Č_{j}^{c})[Z̄_{j}/Ȳ_{j}] ⊗ (¯∃ ; ¯∃X̄_{j} C̀_{j}^{s})[Z̄_{j}/X̄_{j}])). (7.56)

The right-hand sides of (7.44) and (7.54) are, considering (7.55), given by E_{1} and E_{n+1}, respectively. It is possible to prove that E_{1} = E_{n} by showing that E_{1} = E_{2}, E_{2} = E_{3}, …, E_{n} = E_{n+1}. The equality E_{i} = E_{i+1} can be obtained using S_{2} of Definition 110. To this purpose, we consider the following expression for E_{i}

E_{i} =^{def} ¯∃ ; ¯∃Λ⃗(\overbrace{C^{c} ⊗ Cρ̈ ⊗ ⨂_{j=1}^{i−1} (d_{W̄_{j} Z̄_{j}} ⊗ (¯∃ ; ¯∃X̄_{j} C̀_{j}^{s})[Z̄_{j}/X̄_{j}]) ⊗ d_{W̄_{i}Z̄_{i}}}^{C^{i}}

⊗ (¯∃ ; ¯∃Λ⃗_{i} Č_{i}^{c})[Z̄_{i}/Λ⃗_{i}] ⊗ (¯∃ ; ¯∃X̄_{i} C̀_{i}^{s})[Z̄_{i}/X̄_{i}]

⊗ ⨂_{j=i+1}^{n}(d_{W̄_{j} Z̄_{j}} ⊗ (¯∃ ; ¯∃Λ⃗_{j} Č_{j}^{c})[Z̄_{j}/Λ⃗_{j}] ⊗ (¯∃ ; ¯∃X̄_{j} C̀_{j}^{s})[Z̄_{j}/X̄_{j}])), (7.57)

By (7.45), for each i = 1, …, n,:

(¯∃ ; ¯∃Λ⃗_{i} Č_{i}^{c})[Z̄_{j}/Λ⃗_{i}] = ¯∃ ; ¯∃Z̄_{i} C^{i}

thus S_{2} applies giving the desired result. It remains to be proved that C^{s} = ¯∃ ; ¯∃Λ⃗ Č^{s}. For this purpose we define

ρ_{n} =^{def} [Λ⃗/Ȳ] ; [W̄_{1}/Ȳ_{1}] ; ⋯; [W̄_{n}/Ȳ_{n}] ; [Z̄_{1}/X̄_{1}] ; ⋯; [Z̄_{n}/X̄_{n}],

then

C^{s} = ¯∃ ; ¯∃X̄(¯∃ ; ¯∃X̄ Ĉ^{c} ⊗ d_{X̄Ȳ} ⊗ Cρ ⊗ ⨂_{i=1}^{n} (d_{Ȳ_{i}X̄_{i}} ⊗ ¯∃ ; ¯∃X̄_{i} C̀_{i}^{s}))[Λ⃗/X̄]

[by (7.32)]

= ¯∃ ; ¯∃X̄((¯∃ ; ¯∃X̄ Ĉ^{c} ⊗ d_{X̄Ȳ} ⊗ Cρ ⊗ ⨂_{i=1}^{n} (d_{Ȳ_{i}X̄_{i}} ⊗ ¯∃ ; ¯∃X̄_{i} C̀_{i}^{s}))ρ_{n})[Λ⃗/X̄]

[by H_{6}, since FV (Ĉ^{s}) ∩ cod(ρ_{n}) = X̄ ∩ cod(ρ_{n}) = X̄ ∩ dom(ρ_{n}) = ∅]

= ¯∃ ; ¯∃X̄(¯∃ ; ¯∃X̄ Ĉ^{c} ⊗ d_{X̄Λ⃗}

⊗ Cρ̈ ⊗ ⨂_{i=1}^{n} (d_{W̄_{i}Z̄_{i}} ⊗ (¯∃ ; ¯∃X̄_{i} C̀_{i}^{s})[Z̄_{i}/X̄_{i}]))[Λ⃗/X̄]

[by H_{3} and the definition of ρ_{n}]

= ¯∃ ; ¯∃Λ⃗((¯∃ ; ¯∃X̄ Ĉ^{c})[Λ⃗/X̄] ⊗ d_{Λ⃗Λ⃗}

⊗ \overbrace{(Cρ̈ ⊗ ⨂_{i=1}^{n} (d_{W̄_{i}Z̄_{i}} ⊗ (¯∃ ; ¯∃X̄_{i} C̀_{i}^{s})[Z̄_{i}/X̄_{i}]))}^{G}[Λ⃗/X̄])

[by H_{4} and H_{3}]

= ¯∃ ; ¯∃Λ⃗(C^{c} ⊗ Cρ̈ ⊗ ⨂_{i=1}^{n} (d_{W̄_{i}Z̄_{i}} ⊗ (¯∃ ; ¯∃X̄_{i} C̀_{i}^{s})[Z̄_{i}/X̄_{i}]))

[by definition of C^{c} and since FV (G) ∩ X̄ = ∅]

= ¯∃ ; ¯∃Λ⃗ Č^{s}

[by (7.54)]

(7.58)

Therefore, we have the thesis: (C^{c} ↦ C^{s}) ∈ F D̄_{F} ; P_{G}(r). The induction step for (a) can be carried out in a similar way.

Second Inclusion

We now show that F D̄_{F} ; P_{G} ↾ {1, …, # P } ⊆ OD̄_{F}(P, G). Staten differently:

(a) if there exists a magic clause r′ ∈ {# P + 1, …, # P_{m}^{G}} in P_{m}^{G} such that (¯∃ ; ¯∃Λ⃗ Č^{c} ↦ ¯∃ ; ¯∃Λ⃗ Č^{c}) ∈ F D̄_{F} ; P_{G}(r′), then there exists a partial derivation from a goal G_{0} ∈ G of the form G_{0} ⇝_{P}^{∗} Ĉ^{c} □ p(X̄) :: B, such that

¯∃ ; ¯∃Λ⃗ Č^{c} = (¯∃ ; ¯∃X̄ Ĉ^{c})[Λ⃗/X̄]

(b) If there exists a modified clause r of P_{m}^{G} such that the pair (¯∃ ; ¯∃Λ⃗ Č^{c} ↦ ¯∃ ; ¯∃Λ⃗ Č^{s}) belongs to F D̄_{F} ; P_{G}(r), then there exists a partial derivation from a goal G_{0} ∈ G of the form

G_{0} ⇝_{P}^{∗} Ĉ^{c} □ p(X̄) :: B
⇝_{{R_{k}}} Ĉ^{c} ⊗ d_{X̄Ȳ} ⊗ Cρ □ (p_{1}(Ȳ_{1}), …, p_{n}(Ȳ_{n})):: B
⇝_{P}^{∗} Ĉ^{s} □ B, with Ĉ^{s} ≠ 0,

where R_{k} = (p(Ȳ) :− Cρ □ p_{1}(Ȳ_{1}), …, p_{n}(Ȳ_{n})) is a variant of P [r] such that FV (R_{k}) ∩ (FV (G_{0}) ∪ (⋃_{j=1}^{k−1} FV (R_{j}))) = ∅. Moreover we have

¯∃ ; ¯∃Λ⃗ Č^{c} = (¯∃ ; ¯∃X̄ Ĉ^{c})[Λ⃗/X̄] and ¯∃ ; ¯∃Λ⃗ Č^{s} = (¯∃ ; ¯∃X̄ Ĉ^{s})[Λ⃗/X̄]

The proof has the following structure. The hypotheses say that for some r ∈ {1, …, # P_{m}^{G}} we have some e ∈ D_{F} such that e belongs to F D̄_{F} ; P_{G}(r). Since

F D̄_{F} ; P_{G} = ⋃_{i∈ℕ}T D̄_{F} ; P_{G} ↑i,

there must exist k ∈ ℕ such that e ∈ (T D̄_{F} ; P_{G} ↑k)(r). The proof continues by induction on k.

Base case (a). For k = 1 there must exist r′ such that

(¯∃ ; ¯∃Λ⃗ Č^{c} ↦ ¯∃ ; ¯∃Λ⃗ Č^{c}) ∈ (T D̄_{F} ; P_{G}(∅))(r′).

Thus, by definition of T D̄_{F} ; P_{G}, we have P_{m}^{G}[r′] = (m p(Λ⃗) :− Č^{c}). By the definition of magic program, P_{m}^{G}[r′] describes the first call in an initial goal G_{0} ∈ G of the form G_{0} = (Ĉ^{c} □ p(X̄) :: B), where

¯∃ ; ¯∃Λ⃗ Č^{c} = (¯∃ ; ¯∃X̄ Ĉ^{c})[Λ⃗/X̄] = C^{c}.

Base case (b). Here we take k = 2, since each modified clause has at least an atom: the magic one. If, for some r, we have

(¯∃ ; ¯∃Λ⃗ Č^{c} ↦ ¯∃ ; ¯∃Λ⃗ Č^{s}) ∈ (T D̄_{F} ; P_{G} ↑2)(r),  (7.59)

then P_{m}^{G}[r] = (p(Λ⃗) :− ⟨m p(Λ⃗), Cρ̈, ∅⟩) with

¯∃ ; ¯∃Λ⃗ Č^{s} = ¯∃ ; ¯∃Λ⃗(¯∃ ; ¯∃Λ⃗ Č^{c} ⊗ Cρ̈)  (7.60)

where Ūρ̈ = Λ⃗ and Č^{s} ≠ 0, by ⊗_{F} definition (case (ii)). Thus, by T D̄_{F} ; P_{G} definition, there exists a clause r′ defining m p such that

(¯∃ ; ¯∃Λ⃗ Č^{c} ↦ ¯∃ ; ¯∃Λ⃗ Č^{c}) ∈ (T D̄_{F} ; P_{G}(∅))(r′). By the base case (a) there exists a initial goal G_{0} = (Ĉ^{c} □ p(X̄) :: B) such that (7.60) can be rewritten as

¯∃ ; ¯∃Λ⃗ Č^{s} = ¯∃ ; ¯∃Λ⃗(C^{c} ⊗ Cρ̈).  (7.61)

Consider the clause P [r] from which P_{m}^{G}[r] was generated. Let R_{1} = (p(Ȳ) :− Cρ □ ∅) be a variant of P [r] such that FV (G_{0}) ∩ FV (R_{1}) = ∅. Since Ĉ^{c} ≠ 0 by base case (a) there must exist a partial derivation

G_{0} = (Ĉ^{c} □ p(X̄) :: B) ⇝_{\{R_{1}\}} \overbrace{Ĉ^{c} ⊗ d_{X̄Ȳ} ⊗ Cρ}^{Ĉ^{s}} □ B  (7.62)

We now prove that the partial answer constraint of (7.62), suitably renamed, is the success component of the pair in (7.59), namely

¯∃ ; ¯∃Λ⃗(C^{c} ⊗ Cρ̈) = (¯∃ ; ¯∃X̄(Ĉ^{c} ⊗ d_{X̄Ȳ} ⊗ Cρ))[Λ⃗/X̄],  (7.63)

and thus that we also have Ĉ^{s} ≠ 0. Equation (7.63) is identical to (7.25), thus (7.63) holds, as we saw while proving the first implication.

Inductive case (b). We now consider the case k > 2. If for some r we have that

(¯∃ ; ¯∃Λ⃗ Č^{c} ↦ ¯∃ ; ¯∃Λ⃗ Č^{s}) ∈ (T D̄_{F} ; P_{G} ↑k)(r)

and

P_{m}^{G}[r] = (p(Λ⃗) :− ⟨{m p(Λ⃗)}, Cρ̈, (p_{1}(W̄_{1}), …, p_{n}(W̄_{n}))⟩),

then we must have, by T D̄_{F} ; P_{G} definition, that

¯∃ ; ¯∃Λ⃗ Č^{s} = ¯∃ ; ¯∃Λ⃗(¯∃ ; ¯∃Λ⃗ Č^{c} ⊗ Cρ̈ ⊗ ⨂_{i=1}^{n} (d_{W̄_{i}Z̄_{i}} ⊗ (¯∃ ; ¯∃Λ⃗_{i} Č_{i}^{s})[Z̄_{i}/Λ⃗_{i}])),

(7.64) where, by ⊗_{F} definition, Č^{s} ≠ 0 and for each i = 1, …, n,

¯∃ ; ¯∃Z̄_{i}(¯∃ ; ¯∃Λ⃗ Č^{c} ⊗ Cρ̈ ⊗ ⨂_{j=1}^{i−1} (d_{W̄_{j} Z̄_{j}} ⊗ (¯∃ ; ¯∃Λ⃗_{j} Č_{j}^{s})[Z̄_{i}/Λ⃗_{i}]) ⊗ d_{W̄_{i}Z̄_{i}})

= (¯∃ ; ¯∃Λ⃗_{i} Č_{i}^{c})[Z̄_{i}/Λ⃗_{i}]. (7.65)

Moreover, for each i = 1, …, n, we have

Z̄_{i} ≪Λ⃗_{i} FV (P_{m}^{G}[r]) ∪ Λ ∪ Z̄_{1} ∪ ⋯ ∪ Z̄_{i−1},

and Č^{c}, Č_{1}^{s}, …, Č_{n}^{s} are given by the inductive hypotheses as follows.

1. There exists r′ such that P_{m}^{G}[r′] describes a call to p such that

(¯∃ ; ¯∃Λ⃗ Č^{c} ↦ ¯∃ ; ¯∃Λ⃗ Č^{c}) ∈ (T D̄_{F} ; P_{G} ↑(k − 1))(r′)

By the inductive hypothesis, there exists a derivation

G_{0} ⇝_{P}^{∗} Ĉ^{c} □ p(X̄) :: B  (7.66)

such that G_{0} ∈ G and ¯∃ ; ¯∃Λ⃗ Č^{c} = (¯∃ ; ¯∃X̄ Ĉ^{c})[Λ⃗/X̄] = C^{c}.

2. For each i = 1, …, n, there exists r_{i} such that

(¯∃ ; ¯∃Λ⃗_{i} Č_{i}^{c} ↦ ¯∃ ; ¯∃Λ⃗_{i} Č_{i}^{s}) ∈ (T D̄_{F} ; P_{G} ↑(k − 1))(r_{i}).

By the inductive hypothesis, for i = 1, …, n, there exists a derivation from G_{0}^{i} ∈ G

\begin{aligned}G_{0}^{i} & ⇝_{P}^{∗} G_{k_{i}}^{i} = (Ĉ_{i}^{c} □ p_{i}(Y_{i}^{i}) :: B^{i}) \\ & ⇝_{\{R_{k+h_{i}}^{i}\}} Ĉ_{i}^{c} ⊗ dȲ_{i}^{i}X̄_{i}^{i} ⊗ C_{i}ρ_{i}^{i} □ B_{r_{i}}^{i} :: B^{i} \\ & ⇝_{P}^{∗} Ĉ_{i}^{s} □ B^{i}, \end{aligned}  (7.67)

where R_{k+h_{i}}^{i} = (p_{i}(X̄_{i}^{i}) :− C_{i}ρ_{i}^{i} □ B_{r_{i}}^{i}) is a variant of P [r_{i}] such that FV (R_{k+h_{i}}^{i}) ∩ (FV (G_{0}) ∪ (⋃_{j=1}^{k+h_{i}−1} FV (R_{j}^{i}))) = ∅. Furthermore,

¯∃ ; ¯∃Λ⃗_{i} Č_{i}^{c} = (¯∃ ; ¯∃Ȳ_{i}^{i} Ĉ_{i}^{c})[Λ⃗_{i}/Ȳ_{i}^{i}] = C_{i}^{c}

and

¯∃ ; ¯∃Λ⃗_{i} Č_{i}^{s} = (¯∃ ; ¯∃Ȳ_{i}^{i} Ĉ_{i}^{s})[Λ⃗_{i}/Ȳ_{i}^{i}] = C_{i}^{s}.

Now, by (7.67), for each i, …, n, there must exist the following derivation:

1 □ p_{i}(Ȳ_{i}^{i}) ⇝_{{R_{k+h_{i}}^{i}}} 1 ⊗ dȲ_{i}^{i}X̄_{i}^{i} ⊗ C_{i}ρ_{i}^{i} □ B_{r_{i}}^{i} ⇝_{P}^{∗} C̀_{i}^{s} □ ∅, (7.68)

and Ĉ_{i}^{s} = Ĉ_{i}^{c} ⊗ C̀_{i}^{s}. Observe that in (7.68) we have used the same variants employed in (7.67). This implies that FV (C̀_{i}^{s})∩ Ȳ_{i}^{i} = ∅. Then we have:

(¯∃ ; ¯∃Ȳ_{i}^{i} Ĉ_{i}^{s})[Λ⃗_{i}/Ȳ_{i}^{i}] = (¯∃ ; ¯∃Ȳ_{i}^{i}(Ĉ_{i}^{c} ⊗ C̀_{i}^{s}))[Λ⃗_{i}/Ȳ_{i}^{i}]

[by the above result]

= (¯∃ ; ¯∃Ȳ_{i}^{i}(¯∃ ; ¯∃Ȳ_{i}^{i} Ĉ_{i}^{c} ⊗ C̀_{i}^{s}))[Λ⃗_{i}/Ȳ_{i}^{i}]

\left[{by S_{7} since FV (Ĉ_{i}^{c}) ∩ FV (C̀_{i}^{s}) ⊆ Ȳ_{i}^{i} and \atop FV (Ĉ_{i}^{c}) ∩ Ȳ_{i}^{i} ⊆ Ȳ_{i}^{i}}\right]

= (¯∃ ; ¯∃Ȳ_{i}^{i} Ĉ_{i}^{c})[Λ⃗_{i}/Ȳ_{i}^{i}] ⊗ (¯∃ ; ¯∃Ȳ_{i}^{i} C̀_{i}^{s})[Λ⃗_{i}/Ȳ_{i}^{i}]

[by S_{4} and H_{3}]

= ¯∃ ; ¯∃Λ⃗_{i} Č_{i}^{c} ⊗ (¯∃ ; ¯∃Ȳ_{i}^{i} C̀_{i}^{s})[Λ⃗_{i}/Ȳ_{i}^{i}]

[by inductive hypothesis 2]

= (¯∃ ; ¯∃Ȳ_{i}^{i} Ĉ_{i}^{s})[Λ⃗_{i}/Ȳ_{i}^{i}][Z̄_{i}/Λ⃗_{i}]

[by H_{5}]

(7.69)

Thus,

(¯∃ ; ¯∃Ȳ_{i}^{i} Ĉ_{i}^{s})[Z̄_{i}/Ȳ_{i}^{i}] = (¯∃ ; ¯∃Λ⃗_{i} Č_{i}^{c})[Z̄_{i}/Λ⃗_{i}] ⊗ (¯∃ ; ¯∃Ȳ_{i}^{i} C̀_{i}^{s})[Λ⃗_{i}/Ȳ_{i}^{i}][Z̄_{i}/Λ⃗_{i}].

[by (7.69) and H_{3}]

= (¯∃ ; ¯∃Λ⃗_{i} Č_{i}^{c})[Z̄_{i}/Λ⃗_{i}] ⊗ (¯∃ ; ¯∃Ȳ_{i}^{i} C̀_{i}^{s})[Z̄_{i}/Ȳ_{i}^{i}]

[by H_{5}]

(7.70)

We will now show that

¯∃ ; ¯∃Λ⃗ Č^{s} = ¯∃ ; ¯∃Λ⃗(¯∃ ; ¯∃Λ⃗ Č^{c} ⊗ Cρ̈ ⊗ ⨂_{i=1}^{n} (d_{W̄_{i}Z̄_{i}} ⊗ (¯∃ ; ¯∃Ȳ_{i}^{i} C̀_{i}^{s})[Z̄_{i}/Ȳ_{i}^{i}])). (7.71)

Consider the generic expression

E_{i} =^{def} ¯∃ ; ¯∃Λ⃗(¯∃ ; ¯∃Λ⃗ Č^{c} ⊗ Cρ̈ ⊗ ⨂_{j=1}^{i} (d_{W̄_{j} Z̄_{j}} ⊗ (¯∃ ; ¯∃Λ⃗_{j} Č_{j}^{s})[Z̄_{j}/Λ⃗_{j}])

⊗ ⨂_{j=i+1}^{n}(d_{W̄_{j} Z̄_{j}} ⊗ (¯∃ ; ¯∃Ȳ_{j}^{j} C̀_{j}^{s})[Z̄_{j}/Ȳ_{j}^{j}])). (7.72)

The right-hand sides of (7.64) and (7.71) are given by E_{n} and E_{0}, respectively. It is possible to prove that E_{n} = E_{0} by showing that E_{n} = E_{n−1}, E_{n−1} = E_{n−2}, …, E_{1} = E_{0}. The equality E_{i} = E_{i−1} can be obtained using S_{2} of Definition 110. For this purpose, we consider, taking into account (7.70), the following expression for E_{i}

E_{i} =^{def} ¯∃ ; ¯∃Λ⃗(\overbrace{¯∃ ; ¯∃Λ⃗ Č^{c} ⊗ Cρ̈ ⊗ ⨂_{j=1}^{i−1} (d_{W̄_{j} Z̄_{j}} ⊗ (¯∃ ; ¯∃Λ⃗_{j} Č_{j}^{s})[Z̄_{j}/Λ⃗_{j}])}^{C^{i−1}}

⊗ d_{W̄_{i}Z̄_{i}} ⊗ (¯∃ ; ¯∃Λ⃗_{i} Č_{i}^{c})[Z̄_{i}/Λ⃗_{i}] ⊗ (¯∃ ; ¯∃Ȳ_{i}^{i} C̀_{i}^{s})[Z̄_{i}/Ȳ_{i}^{i}]

⊗ ⨂_{j=i+1}^{n}(d_{W̄_{j} Z̄_{j}} ⊗ (¯∃ ; ¯∃Ȳ_{j}^{j} C̀_{j}^{s})[Z̄_{j}/Ȳ_{j}^{j}])). (7.73)

By (7.65), for each i = 1, …, n we have

¯∃ ; ¯∃Z̄_{i} C^{i−1} = (¯∃ ; ¯∃Λ⃗_{i} Č_{i}^{c})[Z̄_{i}/Λ⃗_{i}],

thus S_{2} applies giving the desired result.

The aim is now to show that we can reconstruct an existing derivation whose answer constraint is the right-hand side of (7.71), by composing the partial derivations described in (7.66) and (7.67).

We show this by total induction on the number of calls, n. We start by considering clause P [r], which originated the modified clause P_{m}^{G}[r], and the derivation (7.66):

G_{0} ⇝_{P}^{∗} Ĉ^{c} □ p(X̄) :: B.

Since Ĉ^{c} ≠ 0 (by the inductive hypothesis), there exists a variant of P [r],

R_{k} = (p(Ȳ) :− Cρ □ (p_{1}(Ȳ_{1}), …, p_{n}(Ȳ_{n}))),

where Ūρ = Ȳ, Ū_{1}ρ = Ȳ_{1}, …,Ū_{n}ρ = Ȳ_{n}, and

FV (R_{k}) ∩ (FV (G_{0}) ∪ ⋃_{i=1}^{k−1} FV (R_{i})) = ∅,

such that we have

\begin{aligned}G_{0} & ⇝_{P}^{∗} G_{k} \\ & = Ĉ^{c} □ p(X̄) :: B \\ & ⇝_{\{R_{k}\}} \underbrace{Ĉ^{c} ⊗ d_{X̄Ȳ} ⊗ Cρ}_{Ĉ_{1}^{c}} □ (p_{1}(Ȳ_{1}), …, p_{n}(Ȳ_{n})):: B \end{aligned} (7.74)

If we project Ĉ_{1}^{c} onto the relevant variables Ȳ_{1}, we have that

¯∃ ; ¯∃Ȳ_{1} Ĉ_{1}^{c} = ¯∃ ; ¯∃Ȳ_{1}(Ĉ^{c} ⊗ d_{X̄Ȳ} ⊗ Cρ)

= ¯∃ ; ¯∃Ȳ_{1}(¯∃ ; ¯∃X̄ Ĉ^{c} ⊗ d_{X̄Ȳ} ⊗ Cρ)

[by Lemma 126]

(7.75)

We show that this call-pattern, suitably renamed onto Λ⃗_{1}, is exactly the call-pattern considered in the bottom-up construction. We consider a renaming ρ′_{1} such that

Λ⃗ρ1^{′} = Ȳ , W̄_{1}ρ′_{1} = Ȳ_{1}, …, W̄_{n}ρ′_{1} = Ȳ_{n}.

By (7.65) we can write

¯∃ ; ¯∃Λ⃗_{1} Č_{1}^{c} = (¯∃ ; ¯∃Z̄_{1}(¯∃ ; ¯∃Λ⃗ Č^{c} ⊗ Cρ̈ ⊗ d_{W̄_{1}Z̄_{1}}))[Λ⃗_{1}/Z̄_{1}]

= (¯∃ ; ¯∃Z̄_{1}((¯∃ ; ¯∃Λ⃗ Č^{c} ⊗ Cρ̈)[Z̄_{1}/W̄_{1}])[Λ⃗_{1}/Z̄_{1}]

[by S_{5} with [Z̄_{1}/W̄_{1}] = ρ]

= (¯∃ ; ¯∃W̄_{1}(¯∃ ; ¯∃Λ⃗ Č^{c} ⊗ Cρ̈))[Z̄_{1}/W̄_{1}][Λ⃗_{1}/Z̄_{1}]

[by H_{4}]

= (¯∃ ; ¯∃W̄_{1}(¯∃ ; ¯∃Λ⃗ Č^{c} ⊗ Cρ̈))[Λ⃗_{1}/W̄_{1}]

[by H_{5}]

= (¯∃ ; ¯∃W̄_{1}((¯∃ ; ¯∃X̄ Ĉ^{c})[Λ⃗/X̄] ⊗ Cρ̈))[Λ⃗_{1}/W̄_{1}]

[by inductive hypothesis 1]

= (¯∃ ; ¯∃W̄_{1}(¯∃ ; ¯∃X̄ Ĉ^{c} ⊗ d_{X̄Λ⃗} ⊗ Cρ̈))[Λ⃗_{1}/W̄_{1}]

\left[{by Lemma 124, since FV (Cρ̈) ∩ X̄ = ∅ \atop and FV (Ĉ^{c}) ∩ Λ⃗ = X̄ ∩ Λ⃗ = X̄ ∩ W̄_{i} = ∅}\right]

= (¯∃ ; ¯∃W̄_{1}(¯∃ ; ¯∃X̄ Ĉ^{c} ⊗ d_{X̄Λ⃗} ⊗ Cρ̈)ρ′_{1}[W̄_{1}/Ȳ_{1}])[Λ⃗_{1}/W̄_{1}]

[by H_{6}, since FV (¯∃ ; ¯∃X̄ Ĉ^{c} ⊗ d_{X̄Λ⃗} ⊗ Cρ̈) ∩ cod(ρ′_{1}[W̄_{1}/Ȳ_{1}]) = ∅]

= (¯∃ ; ¯∃W̄_{1}((¯∃ ; ¯∃X̄ Ĉ^{c} ⊗ d_{X̄Ȳ} ⊗ Cρ))[W̄_{1}/Ȳ_{1}])[Λ⃗_{1}/W̄_{1}]

[by the definitions of ρ′_{1}, ρ, and ρ̈]

= ((¯∃ ; ¯∃Ȳ_{1}(¯∃ ; ¯∃X̄ Ĉ^{c} ⊗ d_{X̄Ȳ} ⊗ Cρ))[W̄_{1}/Ȳ_{1}])[Λ⃗_{1}/W̄_{1}]

[by H_{4}]

= (¯∃ ; ¯∃Ȳ_{1}(¯∃ ; ¯∃X̄ Ĉ^{c} ⊗ d_{X̄Ȳ} ⊗ Cρ))[Λ⃗_{1}/Ȳ_{1}]

[by H_{5}]

= (¯∃ ; ¯∃Ȳ_{1} Ĉ_{1}^{c})[Λ⃗_{1}/Ȳ_{1}]

Then we have that ¯∃ ; ¯∃Λ⃗_{i} Č_{1}^{c} = (¯∃ ; ¯∃Ȳ_{1} Ĉ_{1}^{c})[Λ⃗_{1}/Ȳ_{1}] = (¯∃ ; ¯∃Ȳ_{1}^{1} Ĉ_{1}^{c})[Λ⃗_{1}/Ȳ_{1}^{1}] by the inductive hypothesis 2.

The goal G_{k_{1}}^{1} in (7.67) (for i = 1) is (a variant of) G_{k} in (7.74). Therefore, we can continue the derivation as in (7.67), but using a different variant of clause P [r_{1}]:

G_{0} ⇝_{P}^{∗} G_{k} = (Ĉ^{c} □ p(X̄) :: B)
⇝_{{R_{k}}} Ĉ^{c} ⊗ d_{X̄Ȳ} ⊗ Cρ □ (p_{1}(Ȳ_{1}), …, p_{n}(Ȳ_{n})):: B
⇝_{P}^{∗} Ĉ_{1}^{s} □ (p_{2}(Ȳ_{2}), …, p_{n}(Ȳ_{n})):: B,

so we have (¯∃ ; ¯∃Ȳ_{1} Ĉ_{1}^{s})[Λ⃗_{1}/Ȳ_{1}] = ¯∃ ; ¯∃Λ⃗_{1} Č_{1}^{s}. Now, suppose that what we have just proved for the first call holds for all the calls up to the (i − 1)-th one. This means that

\begin{aligned}G_{0} & ⇝_{P}^{∗} & Ĉ^{c} □ p(X̄) :: B \\ & ⇝_{\{R_{k}\}} & Ĉ_{1}^{c} □ (p_{1}(Ȳ_{1}), …, p_{n}(Ȳ_{n})):: B \\ & ⇝_{P}^{∗} & Ĉ_{1}^{s} □ (p_{2}(Ȳ_{2}), …, p_{n}(Ȳ_{n})):: B \\ & = & Ĉ_{2}^{c} □ (p_{2}(Ȳ_{2}), …, p_{n}(Ȳ_{n})):: B \\ & ⇝_{P}^{∗} & . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . \\ & ⋮ & ⋮ \\ & ⇝_{P}^{∗} & . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . \\ & ⇝_{P}^{∗} & Ĉ_{i−1}^{c} □ (p_{i−1}(Ȳ_{i−1}), …, p_{n}(Ȳ_{n})):: B \\ & ⇝_{P}^{∗} & Ĉ_{i−1}^{s} □ (p_{i}(Ȳ_{i}), …, p_{n}(Ȳ_{n})):: B, \end{aligned}  (7.76)

for each j ≤ i − 1 we have

¯∃ ; ¯∃Λ⃗_{j} Č_{j}^{c} = (¯∃ ; ¯∃Ȳ_{j} Ĉ_{j}^{c})[Λ⃗_{j}/Ȳ_{j}],
¯∃ ; ¯∃Λ⃗_{j} Č_{j}^{s} = (¯∃ ; ¯∃Ȳ_{j} Ĉ_{j}^{s})[Λ⃗_{j}/Ȳ_{j}]

where

¯∃ ; ¯∃Ȳ_{j} Ĉ_{j}^{c} = ¯∃ ; ¯∃Ȳ_{j}(Ĉ^{c} ⊗ d_{X̄Ȳ} ⊗ Cρ ⊗ ⨂_{k=1}^{j−1}(d_{Ȳ_{k}X̄_{k}} ⊗ C̀_{k}^{s}))

= ¯∃ ; ¯∃Ȳ_{j}(¯∃ ; ¯∃X̄ Ĉ^{c} ⊗ d_{X̄Ȳ} ⊗ Cρ ⊗ ⨂_{k=1}^{j−1}(d_{Ȳ_{k}X̄_{k}} ⊗ ¯∃ ; ¯∃X̄_{k} C̀_{k}^{s}))

[by Lemma 126]

Similarly, we have also^{10}

¯∃ ; ¯∃Ȳ_{j} Ĉ_{j}^{s} = ¯∃ ; ¯∃Ȳ_{j}(¯∃ ; ¯∃X̄ Ĉ^{c} ⊗ d_{X̄Ȳ} ⊗ Cρ ⊗ ⨂_{k=1}^{j}(d_{Ȳ_{k}X̄_{k}} ⊗ ¯∃ ; ¯∃X̄_{k} C̀_{k}^{s}))

We show that, by (7.76), there exists a derivation

G_{0} ⇝_{P}^{∗} Ĉ_{i−1}^{s} □ (p_{i}(Ȳ_{i}), …, p_{n}(Ȳ_{n})):: B,
= Ĉ_{i}^{c} □ (p_{i}(Ȳ_{i}), …, p_{n}(Ȳ_{n})):: B
⇝_{P}^{∗} Ĉ_{i}^{s} □ (p_{i+1}(Ȳ_{i+1}), …, p_{n}(Ȳ_{n})):: B

such that (¯∃ ; ¯∃Ȳ_{i} Ĉ_{i}^{c})[Λ⃗_{i}/Ȳ_{i}] = ¯∃ ; ¯∃Λ⃗_{i} Č_{i}^{c} and (¯∃ ; ¯∃Ȳ_{i} Ĉ_{i}^{s})[Λ⃗_{i}/Ȳ_{i}] = ¯∃ ; ¯∃Λ⃗_{i} Č_{i}^{s}.

We consider a renaming ρ′_{i} such that

Λ⃗ρ′_{i} = Ȳ , W̄_{1}ρ′_{i} = Ȳ_{1}, …, W̄_{n}ρ′_{i} = Ȳ_{n}, Z̄_{1}ρ′_{i} = X̄_{1}, …, Z̄_{i}ρ′_{i} = X̄_{i}.

Notice that

ρ̈ρ′_{i} = ρ[X̄_{1}/Z̄_{1}] ⋯ ρ[X̄_{i}/Z̄_{i}].

10We assume that, for each j = 1, …, i − 1, the variant of P [r_{j}] employed is R_{k+h_{j}} = (p_{j}(X̄_{j}) :− C_{j}ρ_{j} □ B_{r_{j}} ). However, the only important thing here is that the renaming-apart conditions must hold.

Then we can write

¯∃ ; ¯∃Λ⃗_{i} Č_{i}^{c} = (¯∃ ; ¯∃Z̄_{i}(¯∃ ; ¯∃Λ⃗ Č^{c} ⊗ Cρ̈

⊗ ⨂_{j=1}^{i−1} (d_{W̄_{j} Z̄_{j}} ⊗ (¯∃ ; ¯∃X̄_{j} C̀_{j}^{s})[Z̄_{j}/X̄_{j}]) ⊗ d_{W̄_{i}Z̄_{i}}))[Λ⃗_{i}/Z̄_{i}]

[by (7.65)]

= (¯∃ ; ¯∃Z̄_{i}((¯∃ ; ¯∃Λ⃗ Č^{c} ⊗ Cρ̈

⊗ ⨂_{j=1}^{i−1} (d_{W̄_{j} Z̄_{j}} ⊗ (¯∃ ; ¯∃X̄_{j} C̀_{j}^{s})[Z̄_{j}/X̄_{j}]))[Z̄_{i}/W̄_{i}]))[Λ⃗_{i}/Z̄_{i}]

[by S_{5} with [Z̄_{i}/W̄_{i}] = ρ]

= (¯∃ ; ¯∃W̄_{i}(¯∃ ; ¯∃Λ⃗ Č^{c} ⊗ Cρ̈

⊗ ⨂_{j=1}^{i−1} (d_{W̄_{j} Z̄_{j}} ⊗ (¯∃ ; ¯∃X̄_{j} C̀_{j}^{s})[Z̄_{j}/X̄_{j}])))[Z̄_{i}/W̄_{i}][Λ⃗_{i}/Z̄_{i}]

[by H_{4}]

= (¯∃ ; ¯∃W̄_{i}(¯∃ ; ¯∃Λ⃗ Č^{c} ⊗ Cρ̈

⊗ ⨂_{j=1}^{i−1} (d_{W̄_{j} Z̄_{j}} ⊗ (¯∃ ; ¯∃X̄_{j} C̀_{j}^{s})[Z̄_{j}/X̄_{j}])))[Λ⃗_{i}/W̄_{i}]

[by H_{5}]

= (¯∃ ; ¯∃W̄_{i}(((¯∃ ; ¯∃X̄ Ĉ^{c})[Λ⃗/X̄] ⊗ Cρ̈

⊗ ⨂_{j=1}^{i−1} (d_{W̄_{j} Z̄_{j}} ⊗ (¯∃ ; ¯∃X̄_{j} C̀_{j}^{s})[Z̄_{j}/X̄_{j}]))ρi^{′}[W̄_{i}/Ȳ_{i}]))[Λ⃗_{i}/W̄_{i}]

[by definition of C^{c} and H_{6}, since ρ′_{i}[W̄_{i}/Ȳ_{i}] is a renaming for Č_{i}^{c}]

= (¯∃ ; ¯∃W̄_{i}((¯∃ ; ¯∃X̄ Ĉ^{c})[Ȳ /X̄] ⊗ Cρ

⊗ ⨂_{j=1}^{i−1} (d_{Ȳ_{j} X̄_{j}} ⊗ ¯∃ ; ¯∃X̄_{j} C̀_{j}^{s})[W̄_{i}/Ȳ_{i}])[Λ⃗_{i}/W̄_{i}])

[by H_{3} and definition of ρ′, ρ and ρ̈]

= (¯∃ ; ¯∃Ȳ_{i}(¯∃ ; ¯∃X̄ Ĉ^{c} ⊗ d_{X̄Ȳ}

⊗ \overbrace{Cρ ⊗ ⨂_{j=1}^{i−1} (d_{Ȳ_{j} X̄_{j}} ⊗ ¯∃ ; ¯∃X̄_{j} C̀_{j}^{s})}^{H}))[Λ⃗_{i}/Ȳ_{i}]

\left[{by H_{4} and Lemma 124, since FV (H) ∩ X̄ = \atop FV (Ĉ^{c}) ∩ Ȳ = X̄ ∩ Ȳ = X̄ ∩ Ȳ_{i} = ∅}\right]

= (¯∃ ; ¯∃Ȳ_{i} Ĉ_{i−1}^{s})[Λ⃗_{i}/Ȳ_{i}]

We can thus conclude that there exists a derivation

G_{0} ⇝_{P}^{∗} Ĉ^{c} □ p(X̄) :: B
⇝_{{R_{k}}} Ĉ^{c} ⊗ d_{X̄Ȳ} ⊗ Cρ □ (p_{1}(Ȳ_{1}), …, p_{n}(Ȳ_{n})):: B
⇝_{P}^{∗} Ĉ^{s} □ B,

such that (¯∃ ; ¯∃X̄ Ĉ^{c})[Λ⃗/X̄] = ¯∃ ; ¯∃Λ⃗ Č^{c} and (¯∃ ; ¯∃X̄ Ĉ^{s})[Λ⃗/X̄] = ¯∃ ; ¯∃Λ⃗ Č^{s}, which implies Ĉ^{s} ≠ 0.

The inductive case for (a) can be proved analogously. □

Conclusion

Back in the introduction we have stated our (admittedly ambitious) objectives concerning the analysis of constraint logic-based languages. As the reader will have noticed, there is still much work to do. Whereas we are rather satisfied about the current status of our theoretical treatment, we did not have enough time to complete the experimental part of the project. Indeed, we underestimated both

• the effort required in order to set up the theory: we thought that many of the “needed pieces” were already there, but a closer analysis often revealed that this was not the case; and

• the effort required in order to implement the China.

All the (families) of domains described in this thesis have been implemented and tested, here included a generic implementation of the hierarchy of domains described in Chapter 3. However, up to now the emphasis of the development has been mainly on correctness, though keeping into account that efficiency should finally be obtained. The work on the optimization of the domains has just started. While the generic structural domain of Chapter 4 and the groundness domain of Chapter 6 are already highly optimized, much work is still needed for the numerical domains of Chapter 5. For the latter, extensive experimentation is required. Since our actual implementation (and the theory on which it is based) has several degrees of freedom, finding the right compromise between different techniques is not easy. Similar comments apply to Chapter 7, where we need considerably more empirical comparison among different domains and among different widening policies. Even the domainindependent part of China requires more work. For instance, it applies a sub-optimal version of the magic transformation that causes the unnecessary repetition of some analysis’ work. We are currently modifying the analyzer in order to employ the more efficient Supplementary Magic-Set transformation described in [BR87]. More work is also necessary in the field of fixpoint computation strategies. Just one example that, furthermore, appears not to have been tackled from the theoretical point of view: while there are studies devoted to fixpoint iteration strategies that try to minimize both the analysis work and the application of unnecessary widenings [Bou93, Sch96], no one seems to have studied the strategies that might allow for the efficient application of narrowings [Cou96].

For future work of a more speculative nature, we would like to pursue the possibility of programming the analysis that the work described in chapters 3 and 5 seem to open up. We feel that this ability is a step forward towards our dream about user-defined analysis’ domains. Here, by ‘user’ we mean any programmer, not just the designer of data-flow analyzers. What we have today are particular program analyzers that use a specific domain of properties. While domains for important, program independent, low-level properties such as groundness, aliasing and so forth, are well-known, very little has been done for high-level, user-defined properties. We envisage the possibility of having development environments where

\underline{345}

the program analysis component is able to deal with properties that are not known a priori.

For instance, Prolog programmers develop their own data-struc-tures by freely using the first-order objects of the language. Suppose that a user defines a particular kind of structure: say, a kind of tree. Now, knowing that the representation of some tree is ground might not be enough. Perhaps one wants to know whether a certain tree is almost perfectly balanced, or some other property of the particular structure that the user has devised. Obviously, the analyzer cannot be endowed with all the domains for each property. The user should instead be allowed to provide the system with a description of the properties of interest. The possibility, which is exploited in our approach, of programming both the abstraction function and the abstract domain, might be the right way to go.

Any writing can be improved.

But eventually you have to put something out the door.

— DONALD E. KNUTH, lecturing on Mathematical Writing, Stanford University (1987)

346  Chapter 8. Conclusion

Bibliography

[AH83] G. Alefeld and J. Herzberger. Introduction to Interval Computation. Academic Press, New York, 1983.

[AH92] A. Aiba and R. Hasegawa. Constraint logic programming systems — CAL, GDCC and their constraint solvers. In Proceedings of the International Conference on Fifth Generation Computer Systems (FGCS’92), pages 113–131, Tokyo, Japan, 1992. ICOT.

[AK85] J. F. Allen and H. A. Kautz. A model of naive temporal reasoning. In J. R. Hobbs and R. Moore, editors, Formal Theories of the Commonsense World, pages 251– 268. Ablex, Norwood, NJ, 1985.

[AK91] H. Aït-Kaci. Warren’s Abstract Machine. A Tutorial Reconstruction. The MIT Press, 1991.

[All83] J. F. Allen. Maintaining knowledge about temporal intervals. Communications of the ACM, 26(11):832– 843, 1983.

[AMSS] T. Armstrong, K. Marriott, P. Schachte, and H. Søndergaard. Two classes of Boolean functions for dependency analysis. To appear in Science of Computer Programming. A previous version of this work is available in [AMSS94b].

[AMSS94a] T. Armstrong, K. Marriott, P. Schachte, and H. Søndergaard. Boolean functions for dependency analysis: Algebraic properties and efficient representation. In B. Le Charlier, editor, Static Analysis: Proceedings of the First International Symposium, number 864 in Lecture Notes in Computer Science, pages 266– 280. Springer-Verlag, Berlin, 1994.

[AMSS94b] T. Armstrong, K. Marriott, P. Schachte, and H. Søndergaard. Two classes of Boolean functions for dependency analysis. Technical Report 94/211, Dept. Computer Science, Monash University, Melbourne, 1994.

[Apt92] K. Apt, editor. Logic Programming: Proceedings of the Joint International Conference and Symposium on Logic Programming, MIT Press Series in Logic Programming, Washington, USA, 1992. The MIT Press.

[ASS^{+}88] A. Aiba, K. Sakai, Y. Sato, D. J. Hawley, and R. Hasegawa. The constraint logic programming language CAL. In Proceedings of the International Conference on Fifth Generation Computer Systems (FGCS’88), pages 263–276, Tokyo, Japan, 1988. ICOT.

[Bag92] R. Bagnara. Interpretazione astratta di linguaggi logici con vincoli su domini finiti. M.Sc. dissertation, University of Pisa, July 1992. In Italian.

[Bag94] R. Bagnara. On the detection of implicit and redundant numeric constraints in CLP programs. In M. Alpuente, R. Barbuti, and I. Ramos, editors, Proceedings of the “1994 Joint Conference on Declarative Programming (GULP-PRODE ’94)”, pages 312–326, Peñíscola, Spain, September 1994.

[Bag95a] R. Bagnara. Constraint systems for pattern analysis of constraint logic-based languages. In M. Alpuente and M. I. Sessa, editors, Proceedings of the “1995 Joint Conference on Declarative Programming (GULP-PRODE ’95)”, pages 581–592, Marina di Vietri, Italy, September 1995.

[Bag95b] R. Bagnara. A unified proof for the convergence of Jacobi and Gauss-Seidel methods. SIAM Review, 37(1):93–97, 1995.

[Bag96a] R. Bagnara. A hierarchy of constraint systems for dataflow analysis of constraint logic-based languages. Technical Report TR-96-10, Dipartimento di Informatica, Università di Pisa, 1996.

[Bag96b] R. Bagnara. A reactive implementation of Pos using ROBDDs. In H. Kuchen and S. D. Swierstra, editors, Programming Languages: Implementations, Logics and Programs, Proceedings of the Eighth International Symposium, volume 1140 of Lecture Notes in Computer Science, pages 107–121, Aachen, Germany, 1996. Springer-Verlag, Berlin.

[Bag96c] R. Bagnara. Straight ROBDDs are not the best for Pos. In P. Lucio, M. Martelli, and M. Navarro, editors, Proceedings of the “1996 Joint Conference on Declarative Programming (APPIA-GULP-PRODE ’96)”, pages 493–496, Donostia-San Sebastián, Spain, July 1996.

[Bag97] R. Bagnara. A hierarchy of constraint systems for dataflow analysis of constraint logic-based languages. To appear in Science of Computer Programming. The full version is available in [Bag96a]. A previous version of this work was published in [Bag95a], 1997.

[BCSZ96] R. Bagnara, M. Comini, F. Scozzari, and E. Zaffanella. The AND-compositionality of CLP computed answer constraints. In P. Lucio, M. Martelli, and M. Navarro, editors, Proceedings of the “1996 Joint Conference on Declarative Programming (APPIA-GULP-PRODE ’96)”, pages 355–366, Donostia-San Sebastián, Spain, July 1996.

[BDM92] P. Bigot, S. K. Debray, and K. Marriott. Understanding finiteness analysis using abstract interpretation. In Apt [Apt92], pages 735–749.

[BGL92] R. Bagnara, R. Giacobazzi, and G. Levi. Static analysis of CLP programs over numeric domains. In M. Billaud, P. Castéran, MM. Corsini, K. Musumbu, and A. Rauzy, editors, Actes “Workshop on Static Analysis ’92”, volume 81–82 of Bigre, pages 43–50, Bordeaux, September 1992. Extended abstract.

[BGL93] R. Bagnara, R. Giacobazzi, and G. Levi. An application of constraint propagation to data-flow analysis. In Proceedings of “The Ninth Conference on Artificial Intelligence for Applications”, pages 270–276, Orlando, Florida, March 1993. IEEE Computer Society Press, Los Alamitos, CA.

[Ble74] W. W. Bledsoe. The sup-inf method in Presburger arithmetic. Memo ATP-18, Math. Dept., University of Texas at Austin, Austin, 1974.

[BM83] R. Barbuti and A. Martelli. A structured approach to semantics correctness. Science of Computer Programming, 3:279–311, 1983.

[BMSU86] F. Bancilhon, D. Maier, Y. Sagiv, and J. D. Ullman. Magic sets and other strange ways to implement logic programs. In Proceedings of the ACM Symposium on Principles of Database Systems, pages 1–15, 1986.

[BMV94] F. Benhamou, D. McAllester, and P. Van Hentenryck. CLP(intervals) revisited. In Bruynooghe [Bru94], pages 124–138.

[Bou92] F. Bourdoncle. Abstract interpretation by dynamic partitioning. Journal of Functional Programming, 2(4):407–435, 1992.

[Bou93] F. Bourdoncle. Sémantiques des langages impératifs d’ordre supérieur et interprétation abstraite. Technical Report PRL Research Report 22, DEC Paris Research Laboratory, 1993.

[BR87] C. Beeri and R. Ramakrishnan. On the power of Magic. In Proceedings of the ACM-SIGACT-SIGMOD-SIGART Symposium on Principles of Database Systems, 1987.

[Bru94] M. Bruynooghe, editor. Logic Programming: Proceedings of the 1994 International Symposium, MIT Press Series in Logic Programming, Ithaca, USA, 1994. The MIT Press.

[Bry86] R. E. Bryant. Graph-based algorithms for Boolean function manipulation. IEEE Transactions on Computers, C-35(8):677–691, August 1986.

[Bry92] R. E. Bryant. Symbolic boolean manipulation with ordered binary-decision diagrams. ACM Computing Surveys, 24(3):293–318, September 1992.

[BS81] S. Burris and H. P. Sankappanavar. A Course in Universal Algebra. Springer-Verlag, Berlin, 1981.

[Cap93] Olga Caprotti. RISC-CLP(R-Trees): RISC-CLP(Real) handles symbolic functions. In A. Miola, editor, DISCO’93: International Symposium on Design and Implementation of Symbolic Computation Systems. Springer-Verlag, Berlin, 1993.

[CC77] P. Cousot and R. Cousot. Abstract interpretation: A unified lattice model for static analysis of programs by construction or approximation of fixpoints. In Proceedings of the Fourth Annual ACM Symposium on Principles of Programming Languages, pages 238–252, 1977.

[CC79] P. Cousot and R. Cousot. Systematic design of program analysis frameworks. In Proceedings of the Sixth Annual ACM Symposium on Principles of Programming Languages, pages 269–282, 1979.

[CC92a] P. Cousot and R. Cousot. Abstract interpretation and applications to logic programs. Journal of Logic Programming, 13(2&3):103–179, 1992.

[CC92b] P. Cousot and R. Cousot. Abstract interpretation frameworks. Journal of Logic and Computation, 2(4):511–547, 1992.

[CC92c] P. Cousot and R. Cousot. Comparing the Galois connection and widening/narrowing approaches to abstract interpretation. In M. Bruynooghe and M. Wirsing, editors, Proceedings of the Fourth International Symposium on Programming Language Implementation and Logic Programming, volume 631 of Lecture Notes in Computer Science, pages 269–295, Leuven, Belgium, 1992. Springer-Verlag, Berlin.

[CC95] P. Cousot and R. Cousot. Compositional and inductive semantic definitions in fixpoint, equational, constraint, closure-condition, rule-based and game theoretic form. In P. Wolper, editor, Proceedings of the 7th International Conference on Computer-Aided Verification (CAV ’95), volume 939 of Lecture Notes in Computer Science, pages 293–308. Springer-Verlag, Berlin, 1995. Invited paper.

[CD93] M. Codish and B. Demoen. Analysing logic programs using “Prop”-ositional logic programs and a magic wand. In D. Miller, editor, Logic Programming: Proceedings of the 1993 International Symposium, pages 114–129, Vancouver, Canada, 1993. The MIT Press.

[CDY90] M. Codish, D. Dams, and E. Yardeni. Bottom-up abstract interpretation of logic programs. Technical Report CS90-24, Weizmann Institute of Science, Dept of appl. maths and comp. sci., 1990.

[CDY94] M. Codish, D. Dams, and E. Yardeni. Bottom-up abstract interpretation of logic programs. Theoretical Computer Science, 124(1):93–125, 1994. An earlier version of this work appeared in [CDY90].

[CF92] P. Codognet and G. Filè. Computations, abstractions and constraints. In Proc. Fourth IEEE Int’l Conference on Computer Languages. IEEE Press, 1992.

[CFW91] A. Cortesi, G. Filè, and W. Winsborough. Prop revisited: Propositional formula as abstract domain for groundness analysis. In Proc. Sixth IEEE Symp. on Logic In Computer Science, pages 322–327. IEEE Computer Society Press, 1991.

[CH78] P. Cousot and N. Halbwachs. Automatic discovery of linear restraints among variables of a program. In Proceedings of the Fifth Annual ACM Symposium on Principles of Programming Languages, pages 84–96, 1978.

[CH93] B. Le Charlier and P. Van Hentenryck. Groundness analysis for Prolog: Implementation and evaluation of the domain Prop. In Proceedings of the ACM SIGPLAN Symposium on Partial Evaluation and Semantics-Based Program Manipulation, pages 99–110. ACM Press, 1993.

[CJH94] B. Carlson, S. Janson, and S. Haridi. AKL(FD): A concurrent language for FD programming. In Bruynooghe [Bru94], pages 521–535.

[CL94] C. K. Chiu and J. H. M. Lee. Towards practical interval constraint solving in logic programming. In Bruynooghe [Bru94], pages 109–123.

[Cle87] J. G. Cleary. Logical arithmetic. Future Computing Systems, 2(2):125–149, 1987.

[CLV93] A. Cortesi, B. Le Charlier, and P. Van Hentenryck. Conceptual and software support for abstract domain design: Generic structural domain and open product. Technical Report CS-93-13, Brown University, Providence, RI, 1993.

[CLV94] A. Cortesi, B. Le Charlier, and P. Van Hentenryck. Combinations of abstract domains for logic programming. In Conference Record of POPL ’94: 21st ACM SIGPLAN-SIGACT Symposium on Principles of Programming Languages, pages 227–239, Portland, Oregon, 1994.

[Col82] A. Colmerauer. Prolog and Infinite Trees. In K. L. Clark and S. Å. Tärnlund, editors, Logic Programming, APIC Studies in Data Processing, volume 16, pages 231–251. Academic Press, New York, 1982.

[Col84] A. Colmerauer. Equations and inequations on finite and infinite trees. In Proceedings of the International Conference on Fifth Generation Computer Systems (FGCS’84), pages 85–99, Tokyo, Japan, 1984. ICOT.

[Col90] A. Colmerauer. An introduction to Prolog-III. Communications of the ACM, 33(7):69–90, 1990.

[Cou78] P. Cousot. Méthodes itératives de construction et d’approximation de points fixes d’opérateurs monotones sur un treillis, analyse sémantique de programmes. Thèse d’Ètat, Université Scientifique et Médicale de Grenoble, Grenoble, France, March 1978.

[Cou96] P. Cousot. Efficient narrowing strategies. Personal communication, September 1996.

[CRR92] T. Chen, I. V. Ramakrishnan, and R. Ramesh. Multistage indexing algorithms for speeding Prolog execution. In Apt [Apt92], pages 639–653.

[Dar91] P. W. Dart. On derived dependencies and connected databases. Journal of Logic Programming, 11(1&2):163–188, 1991.

[Dav87] E. Davis. Constraint propagation with interval labels. Artificial Intelligence, 32:281–331, 1987.

[DC93] D. Diaz and P. Codognet. A minimal extension of the WAM for clp(FD). In D. S. Warren, editor, Proc. Tenth Int’l Conf. on Logic Programming, pages 774–790. The MIT Press, 1993.

[DE73] G. B. Dantzig and B. C. Eaves. Fourier-Motzkin elimination and its dual. Journal of Combinatorial Theory (A), 14:288–297, 1973.

[Dea85] T. Dean. Temporal imagery: An approach to reasoning about time for planning and problem solving. Technical Report 433, Yale University, New Haven, CT, 1985.

[Deb89] S. K. Debray. Static inference of modes and data dependencies in logic programs. ACM Transactions on Programming Languages and Systems (TOPLAS), 11(3):418–450, 1989.

[DJBC93] V. Dumortier, G. Janssens, M. Bruynooghe, and M. Codish. Freeness analysis in the presence of numerical constraints. In Tenth International Conference on Logic Programming, pages 100–115. The MIT Press, June 1993.

[DR94] S. K. Debray and R. Ramakrishnan. Abstract interpretation of logic programs using magic transformations. Journal of Logic Programming, 18(2):149–176, 1994.

[DRRS93] S. Dawson, C. R. Ramakrishnan, I. V. Ramakrishnan, and R. C. Sekar. Extracting determinacy in logic programs. In Warren [War93], pages 424–438.

[DVS^{+}88] M. Dincbas, P. Van Hentenryck, H. Simonis, A. Aggoun, T. Graf, and F. Berthier. The constraint logic programming language CHIP. In Proceedings of the International Conference on Fifth Generation Computer Systems (FGCS’88), pages 693–702, Tokyo, Japan, 1988. ICOT.

[EF92] I. Z. Emiris and R. J. Fateman. Towards an efficient implementation of interval arithmetic. Technical Report UCB/CSD 92/693, Computer Science Division (EECS), Berkeley, 1992.

[FGMP95] M. Falaschi, M. Gabbrielli, K. Marriott, and C. Palamidessi. Confluence and concurrent constraint programming. In V. S. Alagar and M. Nivat, editors, Proceedings of the Fourth International Conference on Algebraic Methodology and Software Technology (AMAST’95), volume 936 of Lecture Notes in Computer Science, pages 531–545. Springer-Verlag, Berlin, 1995.

[Fik70] R. E. Fikes. REF-ARF: A system for solving problems stated as procedures. Artificial Intelligence, 1:27–120, [FR94] G. Filé and F. Ranzato. Improving abstract inter-

pretations by systematic lifting to the powerset. In Bruynooghe [Bru94], pages 655–669.

[Fre78] E. C. Freuder. Synthesizing constraint expressions. Communications of the ACM, 21(11):958–966, 1978.

[GDL92] R. Giacobazzi, S. K. Debray, and G. Levi. A generalized semantics for constraint logic programs. In Proceedings of the International Conference on Fifth Generation Computer Systems (FGCS’92), pages 581–591, Tokyo, Japan, 1992. ICOT.

[GDL95] R. Giacobazzi, S. K. Debray, and G. Levi. Generalized semantics and abstract interpretation for constraint logic programs. Journal of Logic Programming, 25(3):191–247, 1995.

[GHK^{+}80] G. Gierz, K. H. Hofmann, K. Keimel, J. D. Lawson, M. Mislove, and D. S. Scott. A Compendium of Continuous Lattices. Springer-Verlag, Berlin, 1980.

[GM92] M. Gabbrielli and M. C. Meo. Fixpoint semantics for partial computed answer substitutions and call patterns. In H. Kirchner and G. Levi, editors, Algebraic and Logic Programming, Proceedings of the Third International Conference, volume 632 of Lecture Notes in Computer Science, pages 84–99, Volterra, Italy, 1992. Springer-Verlag, Berlin.

[GPR95] R. Giacobazzi, C. Palamidessi, and F. Ranzato. Weak relative pseudo-complements of closure operators. Technical Report LIX/RR/95/04, Laboratoire d’Informatique, École Polytechnique, Paris, 1995. Available on WWW at URL

http://www.di.unipi.it/~giaco.

[GR96] R. Giacobazzi and F. Ranzato. Compositional optimization of disjunctive abstract interpretations. In H. R. Nielson, editor, Proceedings of the 1996 European Symposium on Programming, volume 1058 of Lecture Notes in Computer Science, pages 141–155. Springer-Verlag, Berlin, 1996.

[Han93] M. Hanus. Analysis of nonlinear constraints in CLP(R). In Warren [War93], pages 83–99.

[Han96] M. Handjieva. Stan: A static analyzer for CLP(R) based on abstract interpretation. In R. Cousot and D. A. Schmidt, editors, Static Analysis: Proceedings of the Third International Symposium, volume 1145 of Lecture Notes in Computer Science, pages 383–384, Aachen, Germany, 1996. Springer-Verlag, Berlin. System description.

[HCC95] P. Van Hentenryck, A. Cortesi, and B. Le Charlier. Evaluation of the domain PROP. Journal of Logic Programming, 23(3):237–278, 1995. Extended version of [CH93].

[Hic94] T. Hickey. CLP(F) and constrained ODE’s. In Proceedings of the 1994 Workshop on Constraints and Modelling, Ithaca, USA, 1994.

[HM89] Timothy Hickey and Shyam Mudambi. Global compilation of Prolog. Journal of Logic Programming, 7(3):193–230, 1989.

[HMO91] R. Helm, K. Marriott, and M. Odersky. Spatial query optimization: from boolean constraints to range queries. Technical Report RC 17231, IBM Research Division, T. J. Watson Research Center, Yorktown Heights, 1991.

[Hol95] C. Holzbaur. OFAI clp(q,r) manual, edition 1.3.3. Technical Report TR-95-09, Austrian Research Institute for Artificial Intelligence, Vienna, 1995.

[Hon92] H. Hong. Non-linear real constraints in constraint logic programming. In H. Kirchner and G. Levi, editors, Algebraic and Logic Programming, Proceedings of the Third International Conference, volume 632 of Lecture Notes in Computer Science, pages 201–212, Volterra, Italy, 1992. Springer-Verlag, Berlin.

[Hon93] H. Hong. RISC-CLP(Real): Logic programming with non-linear constraints over the reals. In F. Benhamou and A. Colmerauer, editors, Constraint Logic Programming: Selected Research. The MIT Press, 1993.

[HSS^{+}92] W. Havens, S. Sidebottom, G. Sidebottom, J. Jones, and R. Ovans. Echidna: a constraint logic programming shell. In Proceedings of the 1992 Pacific Rim International Conference on Artificial Intelligence, Seoul, 1992.

[Jan94] S. Janson. AKL - A Multiparadigm Programming Language. PhD thesis, Uppsala University, Sweden, June 1994. Also available in the SICS Dissertation Series: SICS/D–14–SE.

[JB92] G. Janssens and M. Bruynooghe. Deriving descriptions of possible values of program variables by means of abstract interpretation. Journal of Logic Programming, 13(2&3):205–258, 1992.

[JBE94] G. Janssens, M. Bruynooghe, and V. Englebert. Abstracting numerical values in CLP(H, N). In M. Hermenegildo and J. Penjam, editors, Proc. Sixth Int’l Symp. on Programming Language Implementation and Logic Programming, volume 844 of Lecture Notes in Computer Science, pages 400–414. Springer-Verlag, Berlin, 1994.

[JL87] J. Jaffar and J.-L. Lassez. Constraint logic programming. In Proceedings of the Fourteenth Annual ACM Symposium on Principles of Programming Languages, pages 111–119. ACM Press, 1987.

[JL89] D. Jacobs and A. Langen. Accurate and efficient approximation of variable aliasing in logic programs. In E. L. Lusk and R. A. Overbeek, editors, Proceedings of the North American Conference on Logic Programming, pages 154–165. The MIT Press, Cambridge, Mass., 1989.

[JM87] J. Jaffar and S. Michaylov. Methodology and implementation of a CLP system. In J.-L. Lassez, editor, Proceedings of the Fourth International Conference on Logic Programming, MIT Press Series in Logic Programming, pages 196–218, Melbourne, Australia, 1987. The MIT Press.

[JM94] J. Jaffar and M. Maher. Constraint logic programming: A survey. Journal of Logic Programming, 19&20:503– 582, 1994.

[JMM91] N. Jørgensen, K. Marriot, and S. Michaylov. Some global compile-time optimizations for CLP(R). In Saraswat and Ueda [SU91], pages 420–434.

[JMSY92a] J. Jaffar, S. Michaylov, P. Stuckey, and R. Yap. An abstract machine for CLP(R). In ACM Programming Language Design and Implementation, volume 27 of SIGPLAN Notices, pages 128–139. ACM Press, 1992.

[JMSY92b] J. Jaffar, S. Michaylov, P. Stuckey, and R. Yap. The CLP(R) language and system. ACM Transactions on Programming Languages and Systems, 14(3):339–395, 1992.

[JS87] N. D. Jones and H. Søndergaard. A semantics-based framework for the abstract interpretation of Prolog. In S. Abramsky and C. Hankin, editors, Abstract Interpretation of Declarative Languages, pages 123–142. Ellis Horwood Ltd, 1987.

[Kan90] T. Kanamori. Abstract interpretation based on Alexander templates. Technical Report TR-549, ICOT, Tokyo, Japan, 1990.

[Kan93] T. Kanamori. Abstract interpretation based on Alexander templates. Journal of Logic Programming, 15(1&2):31–54, 1993. An earlier version of this work appeared in [Kan90].

[KB88] R. A. Kowalski and K. A. Bowen, editors. Logic Programming: Proceedings of the Fifth International Conference and Symposium on Logic Programming, MIT Press Series in Logic Programming, Seattle, USA, 1988. The MIT Press.

[Kei94] T. Keisu. Tree Constraints. PhD thesis, The Royal Institute of Technology, Stockholm, Sweden, May 1994. Also available in the SICS Dissertation Series: SICS/D–16–SE.

[KM81] U. W. Kulisch and W. L. Miranker. Computer Arithmetic in Theory and Practice. Academic Press, New York, 1981.

[Knu80] D. E. Knuth. Seminumerical Algorithms, volume 2 of The Art of Computer Programming. Addison-Wesley, second edition, 1980.

[Lag85] J. C. Lagarias. The computational complexity of simultaneous Diophantine approximation problems. SIAM Journal of Computing, 14(1):196–209, 1985.

[LCVH92] B. Le Charlier and P. Van Hentenryck. Experimental evaluation of a generic abstract interpretation algorithm for Prolog. In Proc. Fourth IEEE Int’l Conference on Computer Languages, pages 137–146. IEEE Press, 1992.

[LL94] J. H. M. Lee and T. W. Lee. A WAM-based abstract machine for interval constraint logic programming and the multiple-trailing problem. In Proceedings of the Sixth IEEE International Conference on Tools with Artificial Intelligence, New Orleans, 1994.

[LM95] G. Levi and D. Micciancio. Analysis of pure PROLOG programs. In M. Alpuente and M. I. Sessa, editors, Proceedings of the “1995 Joint Conference on Declarative Programming (GULP-PRODE ’95)”, pages 521– 532, Marina di Vietri, Italy, September 1995.

[LMM88] J.-L. Lassez, M. J. Maher, and K. Marriott. Unification revisited. In J. Minker, editor, Foundations of Deductive Databases and Logic Programming, pages 587–625. Morgan Kaufmann, Los Altos, Ca., 1988.

[LMY87] C. Lassez, K. McAloon, and R. Yap. Constraint logic programming and option trading. IEEE Expert, 2(3), 1987.

[Mac77] A. K. Mackworth. Consistency in networks of relations. Artificial Intelligence, 8:99–118, 1977.

[Mel85] C. S. Mellish. Some global optimizations for a Prolog compiler. Journal of Logic Programming, 2(1):43–66, 1985.

[MF85] A. K. Mackworth and E. C. Freuder. The complexity of some polynomial network consistency algorithms for constraint satisfaction problems. Artificial Intelligence, 25:65–74, 1985.

[Mon74] U. Montanari. Networks of constraints: Fundamental properties and applications to picture processing. Information Sciences, 7:95–132, 1974.

[Moo66] R. E. Moore. Interval Analysis. Prentice-Hall, Englewood Cliffs, 1966.

[MS88] K. Marriott and H. Søndergaard. On describing success patterns of logic programs. Technical Report 12, The University of Melbourne, 1988.

[MS93] K. Marriott and P. Stuckey. The 3 R’s of optimizing constraint logic programs: Refinement, Removal and Reordering. In Proceedings of the Twentieth Annual ACM Symposium on Principles of Programming Languages, pages 334–344. ACM Press, 1993.

[Mus90] K. Musumbu. Interprétation Abstraite des Programmes Prolog. PhD thesis, Facultés Universitaires Notre-Dame de la Paix – Namur Institut d’Informatique, Belgium, September 1990.

[Nil91] U. Nilsson. Abstract interpretation: A kind of Magic. In J. Małuszyński and M. Wirsing, editors, Proceedings of the 3rd International Symposium on Programming Language Implementation and Logic Programming, volume 528 of Lecture Notes in Computer Science, pages 299–310, Passau, Germany, 1991. Springer-Verlag, Berlin.

[NRC94] Academic careers for experimental computer scientists and engineers. Report of the Committee on Academic Careers for Experimental Computer Scientists, Computer Science and TeleCommunication Board, Commission on Physical Sciences, Mathematics, and Applications — U.S.A. National Research Council, 1994.

[OV93] W. Older and A. Vellino. Constraints arithmetic on real intervals. In F. Benhamou and A. Colmerauer, editors, Constraint Logic Programming: Selected Research. The MIT Press, 1993.

[PB94] G. Pesant and M. Boyer. QUAD-CLP(R): Adding the power of quadratic constraints. In Alan Borning, editor, Principles and Practice of Constraint Programming: Proceedings of the Second International Workshop, volume 874 of Lecture Notes in Computer Science, pages 95–108, Rosario, Orcas Island, USA, 1994. Springer-Verlag, Berlin.

[Pug92] W. Pugh. A practical algorithm for exact array dependence analysis. Communications of the ACM, 35(8):102–114, 1992.

[Ram88] R. Ramakrishnan. Magic Templates: A spellbinding approach to logic programs. In Kowalski and Bowen [KB88], pages 140–159.

[Ric68] D. Richardson. Some undecidable problems involving elementary functions of a real variable. Journal of Symbolic Logic, 33:514–520, 1968.

[RRW90] R. Ramesch, I. V. Ramakrishnan, and D. S. Warren. Automata-driven indexing of Prolog clauses. In Proceedings of the Seventeenth Annual ACM Symposium on Principles of Programming Languages, pages 281– 290, 1990.

[SA89] K. Sakai and A. Aiba. CAL: A theoretical background of constraint logic programming and its applications. Journal of Symbolic Computation, 8:589–603, 1989.

[Sar92] V. A. Saraswat. The category of constraint systems is Cartesian-closed. In Proceedings, Seventh Annual IEEE Symposium on Logic in Computer Science, pages 341– 345, Santa Cruz, California, 1992. IEEE Computer Society Press.

[Sar93] V. A. Saraswat. Concurrent Constraint Programming. The MIT Press, Cambridge, Mass., 1993.

[Sch96] E. Schön. On the computation of fixpoints in static program analysis with an application to analysis of AKL.

Technical Report R95:06, Swedish Institute of Computer Science, 1996.

[Sco82] D. Scott. Domains for denotational semantics. In M. Nielsen and E. M. Schmidt, editors, Proc. ninth Int. Coll. on Automata, Languages and Programming, volume 140 of Lecture Notes in Computer Science, pages 577–613. Springer-Verlag, Berlin, 1982.

[Scu96] M. C. Scudellari. Analisi bottom-up di programmi logici con vincoli basata su trasformazioni Magic-Set. M.Sc. dissertation, R. Bagnara and G. Levi supervisors, University of Pisa, April 1996. In Italian.

[Sho77] R. E. Shostak. On the SUP-INF method in for proving Presburger formulas. Journal of the ACM, 24(4):529– 543, 1977.

[Sho81] R. E. Shostak. Deciding linear inequalities by computing loop residues. Journal of the ACM, 28(4):769–779, 1981.

[SIC95] Swedish Institute of Computer Science, Programming Systems Group. SICStus Prolog User’s Manual, release 3 #0 edition, 1995.

[Sim83] R. Simmons. Representing and reasoning about change in geologic interpretation. Technical Report 749, MIT AI Laboratory, 1983.

[Sim86] R. Simmons. Commonsense arithmetic reasoning. In T. Kehler and S. Rosenschein, editors, Proceedings of the Fifth National Conference on Artificial Intelligence (AAAI-86), volume 1, pages 118–124, Philadelphia, PA, 1986. AAAI Press / The MIT Press. Distributed by Morgan Kaufmann.

[SRP91] V. A. Saraswat, M. Rinard, and P. Panangaden. Semantic foundation of concurrent constraint programming. In Proceedings of the Eighteenth Annual ACM Symposium on Principles of Programming Languages, pages 333–353. Association for Computing Machinery, 1991.

[SS80] G. J. Sussman and G. L. Steele. Constraints: a language for expressing almost hierarchical descriptions. Artificial Intelligence, 14(1):1–39, 1980.

[ST84] T. Sato and H. Tamaki. Enumeration of success patterns in logic programs. Theoretical Computer Science, 34:227–240, 1984.

[SU91] V. Saraswat and K. Ueda, editors. Logic Programming: Proceedings of the 1991 International Symposium, MIT Press Series in Logic Programming, San Diego, USA, 1991. The MIT Press.

[Sut63] I. E. Sutherland. Sketchpad: A man-machine graphical communication system. Technical Report 296, MIT Lincoln Labs, 1963.

[Tay90] Andrew Taylor. LIPS on a MIPS: Results from a Prolog compiler for a RISC. In D. H. D. Warren and P. Szeredi, editors, Logic Programming: Proceedings of the Seventh International Conference on Logic Programming, MIT Press Series in Logic Programming, pages 174– 185, Jerusalem, 1990. The MIT Press.

[VCL94] P. Van Hentenryck, A. Cortesi, and B. Le Charlier. Type analysis of Prolog using type graphs. SIGPLAN Notices, 29(6):337–348, 1994. Proceedings of the ACM SIGPLAN ’94 Conference on Programming Language Design and Implementation.

[VD92] P. VanRoy and A. M. Despain. High-performance logic programming with the Aquarius Prolog compiler. IEEE Computer, 25(1):54, 1992.

[Ver83] S. Vere. Planning in time: Windows and durations for activities and goals. IEEE Trans. Patt. Anal. Mach. Intell., 5(3):246–267, 1983.

[Vod88a] P. Voda. The constraint language Trilogy: Semantics and computation. Technical report, Complete Logic Systems, North Vancouver, BC, Canada, 1988.

[Vod88b] P. Voda. Types of Trilogy. In Kowalski and Bowen [KB88], pages 580–589.

[VSD92a] P. Van Hentenryck, A. V. Saraswat, and Y. Deville. Constraint logic programming over finite domains: the design, implementation, and applications of cc(fd). Technical report, Brown University, Providence, RI, 1992.

[VSD92b] P. Van Hentenryck, H. Simonis, and M. Dincbas. Constraint satisfaction using constraint logic programming. Artificial Intelligence, 58:113–159, 1992.

[Wal75] D. Waltz. Understanding line drawings of scenes with shadows. In P. Winston, editor, The Psychology of Computer Vision, chapter 2. McGraw-Hill, New York, [War62] S. Warshall. A theorem on boolean matrices. Journal

of the ACM, 9(1):11–12, 1962.

[War75] H. S. Warren. A modification of Warshall’s algorithm for the transitive closure of binary relations. Communications of the ACM, 18(4):218–220, 1975.

[War83] D. H. Warren. An abstract Prolog instruction set. Technical Report Note 309, SRI International, 1983.

[War93] D. S. Warren, editor. Logic Programming: Proceedings of the Tenth International Conference on Logic Programming, MIT Press Series in Logic Programming, Budapest, Hungary, 1993. The MIT Press.

[WW96] G. Weyer and W. Winsborough. Annotated structure shape graphs for abstract analysis of Prolog. In H. Kuchen and S. D. Swierstra, editors, Programming Languages: Implementations, Logics and Programs, Proceedings of the Eighth International Symposium, volume 1140 of Lecture Notes in Computer Science, pages 92–106, Aachen, Germany, 1996. Springer-Verlag, Berlin.

[Yem79] Y. Yemini. Some theoretical aspects of positionlocation problems. In Proceedings of the 20th Symposium on the Foundations of Computer Science, pages 1–7, 1979.

[Zaf95] E. Zaffanella. Sharing correctness. Personal communication, December 1995.