• No results found

CCHR: DE SNELSTE CHR IMPLEMENTATIE

N/A
N/A
Protected

Academic year: 2021

Share "CCHR: DE SNELSTE CHR IMPLEMENTATIE"

Copied!
68
0
0

Bezig met laden.... (Bekijk nu de volledige tekst)

Hele tekst

(1)

Katholieke Universiteit Leuven

Faculteit

Ingenieurswetenschappen Departement

Computerwetenschappen

CCHR: DE SNELSTE CHR IMPLEMENTATIE

(2)

Faculteit Ingenieurswetenschappen K.U. Leuven Departement Computerwetenschappen Academiejaar 2006–2007 Celestijnenlaan 200A

3001 Leuven (016) 32 77 00

Naam en voornaam student : Wuille Pieter Titel :

CCHR: De snelste CHR implementatie Engelse vertaling :

CCHR: The fastest CHR implementation ACM Classificatie: D.3.2, D.3.3, D.3.4

Korte inhoud :

Hier komt abstract. Momenteel zijn er slechts enkele stukken geschreven, en citaten, referenties en bibliografie moeten nog toegevoegd worden.

Eindwerk aangeboden tot het behalen van de

graad van burgerlijk ingenieur in de computerwetenschappen Promotor : Prof. Dr. B. Demoen

Assessoren : Dr. ir. T. Schrijvers Prof. Dr. M. Denecker Begeleider : Dr. ir. T. Schrijvers

(3)

Dankwoord

Hier komt mijn dankwoord.

(4)

Inhoudsopgave

1 Inleiding 6.

I Situering 7.

2 Declaratief Programmeren 8.

2.1 Overzicht . . . 8.

2.2 Constraint Programming . . . 8.

2.2.1 Systematisch zoeken . . . 9.

2.2.2 Consistentie technieken . . . 9.

2.2.3 Constraint propagatie . . . 10.

2.3 Logic Programming . . . 10.

2.4 Constraint Logic Programming . . . 10.

3 Constraint Handling Rules 12. 3.1 Inleiding . . . 12.

3.1.1 Syntax . . . 12.

II Bestaande systemen 14. 4 CHR in Prolog 15. 4.1 Geschiedenis . . . 15.

4.2 Het K.U.Leuven CHR systeem . . . 15.

5 CHR in Java 16.

(5)

III CHR in C: CCHR 17.

6 De Taal 18.

6.1 Algemeen . . . 18.

6.2 Syntax . . . 18.

6.2.1 Het cchr-blok . . . 20.

6.2.2 Constraints . . . 20.

6.2.3 Symbolen . . . 21.

6.2.4 Rules . . . 21.

6.3 Variabelen . . . 23.

6.3.1 Waarde en Betekenis . . . 23.

6.3.2 Pointers . . . 23.

6.3.3 Logische variabelen . . . 24.

6.4 C routines . . . 24.

6.4.1 Initializatie en terminatie . . . 24.

6.4.2 Toevoegen van constraints . . . 25.

6.4.3 Reactivatie . . . 25.

6.4.4 Iteratie . . . 25.

7 Ontwerp en implementatie 26. 7.1 Algemeen . . . 26.

7.2 De Compiler . . . 26.

7.2.1 Algemeen . . . 27.

7.2.2 De lexer . . . 27.

7.2.3 De parser . . . 27.

7.2.4 De tussenvorm . . . 28.

(6)

7.3.5 Join ordering . . . 34.

7.3.6 Indexen . . . 34.

7.3.7 Existenti¨ele en universele iteratoren . . . 35.

7.3.8 Matching . . . 36.

7.4 CSM Definities . . . 36.

7.4.1 Noodzakelijke datastructuren . . . 36.

7.4.2 De constraint store . . . 37.

7.4.3 De propagation history . . . 38.

7.4.4 De index . . . 38.

7.5 Runtime . . . 38.

7.5.1 Doubly-linked lists . . . 39.

7.5.2 Logische variabelen . . . 39.

7.5.3 De hashtable . . . 40.

7.5.4 De hashfunctie . . . 41.

8 Voorbeelden en Vergelijkingen 42. 8.1 Benchmarks . . . 42.

8.1.1 Grootst gemene deler . . . 42.

8.1.2 Fibonacci getallen . . . 44.

8.1.3 Priemgetallen . . . 44.

8.1.4 Takeuchi functie . . . 47.

8.1.5 Kleiner-dan-of-gelijk-aan . . . 47.

8.1.6 Ram simulator . . . 49.

8.2 Correctheid . . . 51.

8.2.1 Standaarden . . . 51.

8.2.2 Geheugenlekken . . . 51.

9 Conclusie en toekomstig werk 52. 9.1 Conclusie . . . 52.

9.2 Toekomstig werk . . . 52.

Appendices 54.

(7)

A.1 fib.cchr . . . 54.

A.1.1 De cchr code . . . 54.

A.1.2 De compiler output . . . 54.

A.2 Gebruik van logische variabelen . . . 57.

A.2.1 De Takeuchi functie . . . 57.

A.2.2 Kleiner-dan of gelijk-aan . . . 57.

A.3 De RAM simulator . . . 59.

B CSM - De Constraint Solver Macros 61.

Bibliografie 63.

Lijst van figuren 65.

Lijst van tabellen 66.

(8)

Hoofdstuk 1

Inleiding

Constraint Handling Rules, of CHR, zijn een hoog-niveau, declaratieve taal, oorspronkelijk bedoeld was voor het eenvoudig schrijven van gebruiker-gedefinieerde, applicatie-specifieke “constraint solvers”

in een gegeven programmeertaal, maar worden tegenwoordig meer en meer gebruikt als algemene programmeertaal op zich gebruikt. Ze worden voor een brede waaier van toepassingen gebruikt, oa.

multi-agent systemen, onderzoek naar datatypes, en natural language processing, om er maar enkele te noemen.

“Constraint solvers” zijn hierbij programmas die (effici¨ent) op zoek gaan naar een oplossing, in de vorm van een combinatie van variabelen, die voldoet aan een aantal opgegeven constraints. Dit zijn algemene feiten die bekend zijn en beperkingen opleggen aan de gevraagde variabelen.

CHR op zichzelf is geen volledige taal, slechts een taaluitbreiding die het schrijven van constraint solvers erin vergemakkelijkt. Sinds zijn ontstaan in 1991 zijn er CHR systemen voor vele talen geschreven, voornamelijk voor Prolog en andere declaratieve talen. Momenteel is het meest geavanceerde CHR systeem voor Prolog het K.U.Leuven CHR Systeem, beschikbaar voor een heel aantal Prolog imple- mentaties.

Voor zover wij weten bestond er tot op heden slechts 1 imperatieve programmeertaal waarvoor een CHR systeem bestond: Java. Voor deze populaire object-georienteerde taal bestaan momentel al meerdere implementaties, onder andere het K.U.Leuven JCHR systeem. De imperatieve aard van de taal maakt reeds meer optimalisaties aan het compilatie-proces mogelijk door het gebruik van overschrijvende aanpassing van variabelen, wat in Prolog standaard ontbreekt. Java blijft echter een redelijk hoog-niveau taal die weinig controle over de preciese datastructuren toelaat.

De programmeertaal C werd in het jaar 1972 ontwikkeld als een eenvoudige imperatieve procedurale taal die eenvoudig omgezet kon worden in machinetaal. Na vele standaardisaties (K&R C, ANSI C, ISO C, C99) wordt ze nog steeds veel gebruikt, voornamelijk voor besturingssystemen, systeemsoftware en bepaalde applicaties. Door het gebruik van een gestandaardiseerde voorverwerker en het gebruik van (systeem specifieke) “header” bestanden, kan C broncode tegelijk platformonafhankelijk zijn en zeer laag-niveau mogelijkheden zoals pointers hebben, die directe geheugentoegang bieden.

Het is gebruikelijk dat C programmas eerst gecompileerd worden tot machinetaal vooraleer ze uitgevo- erd worden. Tegenwoordig bestaan er vele C compilers, voor allerlei platformen, die sterk optimalis- eren. Veel recentere programmeertalen kennen “C bindings” wat erin geschreven programmas toelaat C routines aan te roepen. Een CHR systeem voor C zou dus de mogelijkheid kunnen bieden om heel effici¨ente CHR code te schrijven, die bruikbaar is vanuit vele talen.

Het opzet van deze thesis is dus een effici¨ent CHR systeem schrijven dat nauwe interactie met C

(9)

Deel I

Situering

(10)

Hoofdstuk 2

Declaratief Programmeren

2.1 Overzicht

Traditioneel wordt software geschreven in imperatieve programmeertalen. Deze manier van werken vertrekt van het principe dat men de computer opdrachten geeft, en de computer deze ´e´en voor

´

e´en uitvoert. De programmeur blijft echter verantwoordelijk voor een oplossingswijze te vinden voor het probleem dat hij wilt oplossen. Dit is wat men in declaratieve talen probeert te vermijden: de programmeur specifieert enkel het probleem, en de computer zoekt een (effici¨ente) oplossingsstrategie.

In [Kow79] wordt dan ook gesteld: een algoritme is logica + controle. De logica beschrijft waaraan het resultaat moet voldoen, de controle beschrijft hoe dit resutlaat bereikt moet worden. In een declaratieve taal wordt geprobeerd de controle los te koppelen van het programma.

Er zijn verschillende paradigmas die gehanteerd worden om het probleem te beschrijven. Elk geeft aanleiding tot een eigen klasse declaratieve programmeertaal:

• Logic Programming: het probleem wordt omschreven aan de hand van eerste-orde logica.

• Constraint Programming: het probleem wordt omschreven als een reeks beperkingen op een verzameling variabelen over een (eindig) domein.

• Constraint Logic Programming: een combinatie van bovenstaande methodes.

De voordelen van declaratief programmeren zijn duidelijk. Men ontlast de programmeur van hoe een probleem om te lossen. De bekomen programmas zijn voor veel problemen heel eenvoudig, elegant en overzichtelijk. Kennis van hoe de uitvoering van de bekomen programma’s verloopt, kan nuttig zijn om de effici¨ntie op te drijven, maar is in principe niet essentieel om programma’s te kunnen schrijven, wat bij imperatieve talen wel het geval is.

2.2 Constraint Programming

Constraint Programming stelt dat een programma moet bestaan uit een aantal beperkingen die op een aantal variabelen opgelegd worden. Die variabelen zijn dan elementen van een bepaald eindig of oneindig domein, en het zoeken naar oplossingen kan op verschillende manieren gebeuren, afhankelijk van de aard van het probleem. Deze inleiding is gebaseerd op [Bar99].

(11)

worden 2 grote groepen van problemen onderscheiden, Constraint Satisfaction Problems (CSP), en Constraint Solving. Het belangrijkste verschil tussen beiden is de grootte van het domein waarover het probleem handelt. Bij een CSP is dit een eindige verzameling, waarvan de mogelijke waarden niet noodzakelijk numeriek hoeven te zijn. De oplossingsstrategie bestaat dan ook algemeen gezien uit het overlopen van verschillende mogelijkheden of het beperken van mogelijkheden tot er zo weinig mogelijk overblijven. Bij Constraint Solving zijn de domeinen complexer, en het overlopen van combinaties is ongeschikt om deze op te lossen. Er wordt dan ook meer beroep gedaan op wiskundige methodes.

Bij het zoeken naar oplossingen voor CSP, kan het zijn dat men ge¨ınteresseerd is in een arbitraire oplossing, in alle oplossingen, of in de beste (of althans zo goed mogelijke) oplossing. Een oplossing bestaat uit een reeks variabele-toewijzingen zodat alle variabelen tegelijk aan alle opgelegde beperkin- gen voldoen.

2.2.1 Systematisch zoeken

De eenvoudigste manier om een CSP op te lossen is simpelwel mogelijkheden af te lopen en te con- troleren of aan de beperkingen voldaan is. Deze methode heet “Generate and Test”. Indien er een conflict gevonden wordt (een constraint waar niet aan voldaan is), wordt een nieuwe waardentoewijz- ing gezocht. Het is een heel simpele methode, die zekerheid biedt 1 of zelfs alle oplossingen te vinden, maar vraagt zeer veel tijd. Het is mogelijk deze techniek te versnellen, door het genereren intelligenter te maken, en zo minder conflicterende oplossingen te veroorzaken. Een andere mogelijkheid is de variabelen niet allen tegelijk, maar ´e´n voor ´e´en een waardetoewijzing geven, en zodra alle variabelen betrokken in een bepaalde constraint een toewijzing krijgen, de geldigheid van deze constraint nagaan.

Indien dan een conflict optreedt, wordt naar de laatste variabele waar nog niet alle mogelijkheden voor geprobeerd waren teruggekeerd, en daarvoor de volgende mogelijkheid gekozen. Deze techniek, die

“backtracking” genoemd wordt, is duidelijk beter dan pure Generate and Test, want er worden hele deelverzamelingen oplossingen reeds op voorhand uitgesloten. Ze leidt echter nog steeds vaak tot exponenti¨ele tijdscomplexiteit.

2.2.2 Consistentie technieken

Een andere methode om tot een oplossing te komen, zijn de consistentie technieken. Deze komen neer op bijhouden welke mogelijkheden elke variabele nog heeft, en (vereenvoudigde) constraints ge- bruiken om mogelijkheden hiervan te snoeien. Er bestaan verschillende gradaties van consistentie technieken, die steeds ingewikkeldere vormen van inconsistenties op voorhand kunnen verwijderen uit de mogelijkheden. Er kan echter aangetoond worden dat deze technieken niet compleet zijn, maw.

niet gegarandeerd de oplossing kunnen vinden. Ze worden dan ook meestal gebruikt in combinatie met andere technieken om de overgebleven mogelijkheden af te gaan, indien consistentie technieken niet tot een volledige oplossing kunnen komen.

(12)

2.2.3 Constraint propagatie

Systematisch zoeken is in staat altijd een oplossing te vinden, maar kan daar zeer lang over doen.

Er zijn enkele bekende redenen waarom deze strategie overbodig veel werk vraagt. Zo is er trash- ing, het herhaaldelijk conflicten vinden om telkens dezelfde reden. Er is ook veel redundant werk, nl. conflicterende waardes die niet onthouden worden, en ten laatste het laattijdig ontdekken van conflicten.

Consistentietechnieken daarentegen beperken de zoekruimte snel, maar komen zeker niet altijd tot de oplossing. Daarom worden ze zoals gezegd meestal samen gebruikt. Er zijn hierbij 2 basistechnieken, Look Back en Look Ahead schemas.

Bij Look Back, worden consistentie technieken gebruikt tijdens het itereren, om zo conflicten tussen de variabelen die reeds een waarde toegewezen kregen te ontdekken. Backtracking is de eenvoudigste techniek hiervoor, die gewoon probeert inconsistenties te ontdekken en indien ja voortgaat met de volgende waarde voor de laatste variabele. Backjumping verbetert deze techniek nog, door te anal- yseren welke variabelen het conflict veroorzaakten, en wanneer geen mogelijkheden overblijven terug te springen naar de conflicterende variabele in plaats van de laatste variabele. Er zijn nog enkele andere verbeteringen, zoals backchecking en backmarking, die gebaseerd zijn op het onthouden van conflicterende waardes, zodat ze niet opnieuw gecontroleerd moeten worden.

Alle methodes ontdekken echter nog steeds pas een conflict nadat het optreedt. Daarom gaat men in Look Ahead schemas consistentie technieken gaan toepassen op combinaties van variabelen die al wel en variabelen die nog geen waarde toegekend kregen, om zo toekomstige mogelijkheden te gaan uitsluiten, nog voor ze optreden. Hier zijn verschillende varianten van, die meestal AC technieken gebruiken. Voor meer informatie verwijzen we naar de literatuur.

2.3 Logic Programming

De volgende vorm van declaratief programmeren waar we even op zullen ingaan heet Logic Program- ming, of logisch programmeren. Deze techniek is gebaseerd op de wiskundige theorie van predikaat- logica.

TODO1 TODO

2.4 Constraint Logic Programming

Constraint Logic Programming (CLP) is een combinatie van Constraint Programming en Logic Pro- gramming. Aangezien beiden instanties zijn van hetzelfde probleem: “geef me een oplossing die voldoet aan de eisen die gesteld worden”, is deze combinatie heel natuurlijk. In CLP breidt men de mogeli- jkheden van de logische taal uit, door toe te laten dat constraints gebruikt worden in de bodies van een clause.

Het werkt door een “constraint store” toe te voegen aan de toestand van de taal, die bv. mee hersteld wordt na het backtracken. Aan deze constraint store kunnen dan constraints toegevoegd worden door zogenaamde “tell constraints”, en opgevraagd worden met “ask constraints”. De oorspronkelijke toestand van de constraint store is leeg, of “true”. Een tell constraint past dan de toestand van de store aan zodat aan de opgelegde constraint voldaan wordt, en laat het programma voortlopen zolang de constraint store in een consistente staat is, zijnde (voor alle variabelen) minstens 1 mogelijkheid

(13)

bevat. Een ask constraint daarentegen past de toestand van de store niet aan, maar gaat alleen voort indien de gevraagde constraint door de store ge¨ımpliceerd wordt. Een dergelijke implicatie kan bv.

gecontroleerd worden door de negatie van de gevraagde constraint toe te voegen aan de constraint store, en te zien of er mogelijkheden overblijven. Indien niet, dan is de gevraagde constraint ge¨ımpliceerd.

Prolog kan in principe zelf ook als CLP taal gezien worden, met (in basis-Prolog) slechts 1 tell con- straint (=, of unificatie) en 1 ask constraint (==, of controle of gelijkheid).

(14)

Hoofdstuk 3

Constraint Handling Rules

3.1 Inleiding

De eerste constraint solvers volgden het zogenaamde black box principe: men kon constraints opgeven en de constraint solver, die specifiek voor de gebruikte constraints over een vastgesteld domein gemaakt was, lostte ze op. Hierbij was het nauwelijks mogelijk om inzicht te krijgen hoe de constraint solvers zelf werkten, en zeker moeilijk of onmogelijk om er aanpassingen aan te maken. Men had echter nood aan constraint solvers die specifiek voor een bepaalde applicaties problemen konden oplossen. In een volgende geneneratie solvers, werd dit probleem opgelost door meer controle over de solver in handen van de programmeur te leggen. De gebruikte zogenaamde glass box technieken en evolutie wordt uitgelegd in [Fr¨u98]. Hierin haalt men ook de no box strategie aan: de werking van de constraint solver ligt volledig in handen van de programmeur.

CHR is een hoog-niveau, declaratieve taaluitbreiding voor CLP. Het geeft de mogelijkheid om in een gegeven host-taal die reeds enkele built-in constraints ondersteunt, nieuwe door de gebruiker gedefini¨eerde constraints (ofwel CHR constraints) te gaan toevoegen. CHR geeft dan een mogeli- jkheid te specifi¨eren hoe deze CHR constraints, die door een programma in de host-taal toegevoegd kunnen worden, omgezet worden in verschillende stappen tot nieuwe CHR constraints, en built-in con- straints. In wezen bestaat de uitvoering van een CHR programma uit een herhaaldelijke toepassing van transformatie-regels die op de gegeven constraints uitgevoerd worden tot ze opgelost zijn.

CHR staat voor Constraint Handling Rules, en bestaat uit een opsomming van regels die beschrijven hoe en wanneer CHR constraints herschreven mogen worden tot andere constraints. We zullen hier een beschrijving geven van CHR in te bedden in Prolog, wat gebruikelijk is. Op die manier kan de CHR syntax en semantiek het gemakkelijkst beschreven worden. Bij het inbedden van CHR in een imperatieve taal zijn vaak enkele omwegen nodig om de hosttaal als CLP taal te kunnen gebruiken.

De built-in constraints die Prolog aanbiedt zijn zijn = (unificatie), == (termgelijkheid), gebruiker- gedefinieerde predikaten en enkele ingebouwde predikaten.

3.1.1 Syntax

Constraints nemen de vorm aan van predikaten, met een naam en een ariteit. Ze moeten op een of andere manier gedeclareerd worden als constraint, ipv. normaal predikaat. In Prolog CHR gebeurt dat met:

(15)

Hiermee wordt duidelijk gemaakt dat upto/1 en prime/1 geen predikaten maar constraints zijn.

Voor de rest kunnen er nu CHR rules gegeven worden, die beschrijven hoe de gebruikers-gedefinieerde constraints fib/2 behandeld moet worden. Er zijn 3 types van CHR rules:

• Simplification: Een regel die aangeeft dat onder een bepaalde voorwaarden, bepaalde CHR constraints verwijderd mogen worden en er andere in de plaats komen. De algemene syntax is:

naam@R1, R2, . . . <=> G1, G2, . . . |A1, A2, . . . .. Ri zijn hierbij de constraints na het uitvoeren van de regel verwijderd mogen worden (“removed constraints”), Gi de voorwaardes waaraan voldaan moet zijn (“guards”), en Ai de constraints die toegevoegd moeten worden (“added constraint”). Zulk een regel wordt gebruikt om een vereenvoudiging toe te laten. Het naam@

gedeelte is optioneel, net als het Gi, . . . | gedeelte, indien er geen Gi’s zijn.

• Propagation: een regel die dat onder bepaalde voorwaardes, bepaalde CHR constraints toegevoegd mogen worden. De syntax is:

naam@K1, K2, . . . ==> G1, G2, . . . |A1, A2, . . . .. Ki zijn hierbij de constraints die reeds moeten bestaan (“kept constraints”), Gi en Ai zijn gelijkaardig aan het vorige voorbeeld.

• Simpagation: Een combinatie van Propagation en Simplification. De syntax is

naam@K1, K2, . . . \R1, R2, . . . <=> G1, G2, . . . |A1, A2, . . . .. Hierbij moeten zowel de kept als de removed constraints bestaan, en aan de guards voldaan zijn, en zullen de removed constraint verwijderd worden en de added constraints toegevoegd.

Een vollediger voorbeeldje:

simplification @ upto(X) <=> X<2 | true.

propagation @ upto(X) ==> X>1 | Y is X-1, upto(Y), prime(X).

simpagation @ prime(X) \ prime(Y) <=> Z is Y mod X, Z==0 | true.

(16)

Deel II

Bestaande systemen

(17)

Hoofdstuk 4

CHR in Prolog

In dit hoofdstuk wordt een kort overzicht gegeven van de bestaande CHR implementaties in Prolog.

4.1 Geschiedenis

De eerste volledige CHR implementatie, zoals beschreven in [HF00], werd gemaakt voor SICStus prolog (meer informatie in [Int03]). Deze werd geschreven door Christian Holzbaur en Thom Fr¨uhwirth, en wordt algemeen als de referentie-implementatie beschouwd. Een compatibel systeem werd later geschreven voor Yap [SDRA04]. Een oudere, onvolledige versie werd ook geschreven voor ECLiPSe [IP].

Een nieuwere implementatie was het K.U.Leuven CHR systeem [SD04]. Het werd oorspronkelijk voor hProlog, maar bestaat ondertussen ook voor XSB Prolog [W+05] en SWI-Prolog [Wie06], zoals beschreven in [SWD05]).

4.2 Het K.U.Leuven CHR systeem

Al deze systemen maken gebruik van een vertaling van CHR regels naar Prolog zelf, die dan binnen de respectievelijke Prolog runtime uitgevoerd kan worden.

(18)

Hoofdstuk 5

CHR in Java

(19)

Deel III

CHR in C: CCHR

(20)

Hoofdstuk 6

De Taal

In dit hoofdstuk wordt ingegaan op de taal ontwikkeld om CHR in C mogelijk te maken: CCHR.

Het doel van deze thesis was nagaan hoeveel effici¨enter dan bestaande CHR systemen een implemen- tatie in C kon zijn. De doelstellingen waar de taal aan moet voldoen zijn dan ook:

• Een taal ontwerpen die mogelijk maakt CHR en C te integreren: de CHR code moet C als host language kunnen gebruiken (C constructies als “built-in constraints” kunnen behandelen), en de C code moet bijvoorbeeld omgekeerd ook CHR constraints kunnen laten toevoegen.

• De taal moet het mogelijk maken heel efficiente code te genereren.

6.1 Algemeen

Om de integratie van C en CHR mogelijk te maken, wordt een “cchr” blok toegelaten binnenin een stuk normale C code, wat vervangen zal worden door een cchr-compiler door een stuk equivalente pure C code. In die zin moet CCHR dan ook eerder als een taal-uitbreiding van C beschouwd worden, en niet als aparte taal. Alle C taal-elementen die de omgeving wijzigen in de “omhullende” C code zijn automatisch ook van toepassing op CHR (bv. #include directives).

6.2 Syntax

De algemene syntax bedacht om CHR met C te kunnen integreren, is sterk ge¨ınspireerd door K.U.Leuven JCHR, waar CHR handlers geschreven worden binnen een specifiek blok in een bestand met .jchr extensie. In CCHR zal echter niet ge¨eist worden dat de CHR handler code zich in een apart be- stand bevindt, om de mogelijke interacties tussen C en CHR niet te beperken. C verschilt hierin van Java, aangezien in C niet enkel functie-aanroepen zinvolle interacties zijn (zoals methode-aanroepen in Java), maar ook macro-definities, inline-functies, . . . , die enkel mogelijk zijn of betekenis hebben binnen hetzelfde bronbestand.

Om de syntax in te leiden, is het interessant te beginnen met een eenvoudig voorbeeld (zie voor- beeld 6.1). Dit programma berekent opeenvolgende “getallen van Fibonacci”. Deze getallen zijn gedefinieerd als een rij waarbij elk getal de som is van de 2 voorgaande, beginnende met 2 maal een 1.

(21)

1 #include <stdio.h>

2 #include <stdlib.h>

3

4 #include "fib_cchr.h"

5

6 cchr {

7 constraint fib(int,long long),init(int);

8

9 begin @ init(_) ==> fib(0,1LL), fib(1,1LL);

10 calc @ init(Max), fib(N2,M2) \ fib(N1,M1) <=>

11 alt(N2==N1+1,N2-1==N1), N2<Max |

12 fib(N2+1, M1+M2);

13 }

14

15 int main(int argc, char **argv) {

16 cchr_runtime_init();

17 cchr_add_init_1(90);

18 cchr_consloop(j,fib_2,{

19 printf("fib(%i,%lli)\n",

20 cchr_consarg(j,fib_2,1),

21 (long long)cchr_consarg(j,fib_2,2));

22 });

23 cchr_runtime_free();

24 return 0;

25 }

Codevoorbeeld 6.1: Fibonacci-voorbeeld

(22)

6.2.1 Het cchr-blok

Een CCHR bronbestand bevat steeds een “cchr”-blok, ingeluid met het sleutelwoord cchr en gevolgd door een stuk code tussen accolades. Dit stuk code zal vervangen worden door een equivalent stuk C broncode. Er zal tevens een header gegenereerd worden met dezelfde bestandsnaam als het bronbe- stand, maar .cchr vervangen door cchr.h, en definities bevatten zodat met de CHR handler gein- terageert kan worden. In voorbeeld 6.1 kan u het cchr blok zien op lijn 6 tot lijn 13. Lijn 4 bevat de opname van dat header bestand. Om problemen met recursieve definities op te lossen, is het nodig dit bestand op te nemen voor het cchr blok.

Binnen het cchr-blok gelden dezelfde algemene regels als in C zelf:

• Commentaar wordt begonnen door // (tot op het einde van de lijn), of door /* (tot aan de eerstvolgende */).

• Spaties en andere witruimte (nieuwe lijnen) hebben geen betekenis (tenzij als scheiding tussen 2 symboolnamen of operatoren).

6.2.2 Constraints

Constraints (zoals op lijn 7 van het voorbeeld), volgen JCHR’s syntax: het constraint sleutelwo- ord gevolgd door een lijst van 1 of meer constraint namen, met tussen haakjes hun respectievelijke argumenten-types. Constraints zonder argument vereisen nog steeds () erachter, net zoals C ook een lege argumentenlijst vereist voor functies zonder argumenten (dit verschil van JCHR).

Het is ook mogelijk om enkele opties aan te geven over constraints. Deze worden genoteerd door achter de argumenten van een constraint “option(optienaam,args. . . )” te zetten (meerdere options mogelijk). Een lijstje van de toegelaten opties:

• fmt: een standaard C printf formatstring, voor gebruik in debugmodus, om constraint suspen- sions te kunnen uitprinten. Na deze formatstring volgen de argumenten dat de formatstring zelf nodig heeft te komen, waarbij naar de argumenten van de uit te printen constraints verwezen

kan worden met $1, $2, . . . . TODO1 TODO

• init: Een stuk C code dat uitgevoerd wordt bij het aanmaken van een constraint suspension van dit type. het kan een functie, een macro of gewoon een stuk code zelf zijn.

• destr: Een stuk C code dat uitgevoerd wordt bij het vernietigen van een constraint suspension van dit type.

• add: Een stuk C code dat uitgevoetd bij bij het toevoegen van een constraint suspension van dit type in de constraint store. Hierbij kan naar de huidige constraint suspension verwezen worden met $0 ($0 is van het type cchr id t).

• kill: Een stuk C code dat uitgevoerd wordt bij het verwijderen van een constraint suspension van dit type uit de constraint store.

Het is niet mogelijk (in tegenstelling tot JCHR) om constraints met infix notatie te gebruiken. C zelf ondersteunt ook geen “operator overloading”, dus deze functionaliteit leek overbodig.

1uitleg debugmodus

(23)

6.2.3 Symbolen

Geldige namen voor constraints, functies, variabelen en andere C symbolen zijn letters (kleine en hoofdletters), cijfers en de underscore ( ). Het eerste teken mag echter geen cijfer zijn. Alle namen die met een hoofdletter beginnen kunnen als CHR variabele gebruikt worden, maar een naam (die nog niet eerder voorkwam) op een plaats gebruiken waar geen variabele gedefinieerd kan worden (zie verder) zal ervoor zorgen dat die als extern C symbool beschouwd wordt. Het is ook mogelijk een bepaalde naam sowieso als extern symbool te doen beschouwen, door het achter een “extern” sleutelwoord te zetten binnen het cchr-blok.

6.2.4 Rules

De syntax voor het noteren van CCHR regels is grotendeels gebaseerd op JCHR (waarvan de syntax sterk aanleunt bij de originele CHR syntax). Ze bestaat uit

1. Een (optionele) benaming voor de regel, gevolgd door een @-symbool.

2. Een of meerdere head-constraints, met argumenten (CHR variabelen of C expressies, zie verder), gescheiden door komma’s.

3. Eventueel een backslash (\) gevolgd door nog een of meer head-constraints (removed constraints, in geval van simpagation rule)

4. Een regel-type aanduider (==> voor propagation of <=> voor simplification of simpagation).

5. Eventueel een guard, gevolgd door een vertikaal streepje (|).

6. De body van de CHR regel.

7. Afgesloten mbv. een puntkomma (;).

Er zijn enkele verschillen met JCHR:

• De regels staan niet in een apart rules blok. Dit lijkt een overbodige erfenis uit JaCK.

• Regels eindigen niet op een punt maar op een puntkomma (Dit zorgt voor ambigu¨ıteit aangezien het punt een geldige C operator is).

Head

(24)

Guard en Body

Er is grote vrijheid aan wat als guard of body bruikbaar is in CCHR:

1. (enkel als guard) Een willekeurige C expressie, die tot 0 of niet-0 evalueert (false of true).

2. Een lokale variabele definitie.

3. Een stuk arbitraire C code (tussen accolades).

4. (enkel als body) Een toe te voegen constraint

Binnen een guard is er een speciaal sleutelwoord mogelijk: alt: het dient om 2 of meer logisch equivalente expressies te geven. Dit kan optimalisaties helpen, die steunen op een bepaalde betrekking die uit een guard afgeleid kan worden.

Lokale variabelen binnen een guard of body gebruiken kennen een eigen syntax, die verschilt van die van JCHR. Het volstaat om een datatype, gevolgd door een variabelenaam (die niet eens met een hoofdletter moet beginnen), een gelijkheidsteken, en eventueel een expressie voor initializatie te noteren. Een voorbeeldje:

calc @ init(Max), fib(N,A) \ fib(N+1,B) <=> int sum=A+B, fib(N+2,sum);

Macros

Het is mogelijk om binnen het CCHR blok zelf verkorte notaties in te voeren voor gebruik binnen de body van CHR rules. Een voorbeeldje:

macro eq(bigint_t,bigint_t) bigint_cmp($1,$2);

macro eq(_,_) ($1==$2);

Dit zal het mogelijk maken om 2 bigint t variabelen met elkaar te vergelijken mbv. eq(a,b), maar ook 2 andere variabelen mbv. de C operator ==. (het datatype bigint t zou elders gedefinieerd moeten zijn).

Zoals te zien is laat deze techniek toe dat verschillende data-types als parameters gebruikt worden (een soort polymorfisme), of dat als joker voor elk willekeurig type kan dienen. Indien er meerdere macrodefinities van toepassing zijn, wordt de eerste gebruikt. De datatypes zijn echter enkel bekend voor pure variabelen, gedefini¨eerd in de head van een regel, of als lokale variabele. Ingewikkeldere expressies geen loutere variabele zijn, kunnen enkel overeenkomen met het jokertype .

Het nut van deze macros is een equivalent voorzien voor de built-in constraints van JCHR. In een latere uitbreiding zouden deze macros automatisch gegenereerd kunnen worden door het inladen van een extra module.

De macro eq is trouwens voorgedefinieerd om binaire equivalenties voor te stellen. Dit is een uitbreiding van de C operator == voor samengestelde datatypes (zoals structs).

(25)

6.3 Variabelen

6.3.1 Waarde en Betekenis

In C is de waarde van een variabele altijd gewoon de inhoud van het geheugenadres waar de variabele opgeslagen is. In CCHR wordt ge¨eist dat de waarde van constraint-argumenten constant is. Deze eis laten zou voor veel moeilijkheden kunnen zorgen, waaronder moeilijk te defini¨eren gedrag. Dat wilt daarom echter niet zeggen dat het onmogelijk is om de betekenis van een variabele te wijzigen, die van meer kan afhangen dan enkel de waarde.

Dat laatste is vaak nodig om in CHR het resultaat van een berekening terug te geven. Zoals in dit voorbeeld (in Prolog-CHR):

calcMin1 @ min(N1,N2,R) <=> N1=<N2 | R=N1.

calcMin2 @ min(_,N2,R) <=> R=N2.

In dit voorbeeld zal het toevoegen van min(A,B,C) tot gevolg hebben dat R ge¨unificeerd wordt met de kleinste van A en B.

Wanneer de betekenis van variabelen kan veranderen, en dat is waar guards indirect op controleren, kan het mogelijk zijn dat reactivatie nodig is.

6.3.2 Pointers

Een van de belangrijkste opzichten waarin C van recentere programmeertalen verschilt, is de mogeli- jkheid tot direct geheugenbeheer. Een C-programmeur kan zijn programma eender wat laten doen met het deel (virtueel) geheugen dat het (kan) krijgen van het systeem. Of dat de begrijpbaarheid van de erin geschreven programma’s ten goede komt, wordt hier in het midden gelaten, maar het blijft een taalconstructie die voor veel mogelijkheden zorgt.

Pointers zijn 1 mogelijkheid om een constraint argument met vaste waarde toch van betekenis te doen veranderen. In CCHR zou bovenstaand voorbeeld opgelost kunnen als volgt:

constraint min(int,int,int*); /* int* = pointer to int */

calcMin1 @ min(N1,N2,R) <=> N1<=N2 | { *(R)=N1 };

calcMin2 @ min(_,N2,R) <=> { *(R)=N2 };

In dit voorbeeld is het laatste argument van min een pointer naar een int waarin het resultaat geplaatst wordt. De waarde van dat laatste argument blijft zolang de constraint bestaat hetzelfde, zijnde een verwijzing naar dezelfde geheugenplaats, maar de betekenis – zijnde hetgeen op die bepaalde plaats

(26)

(groter dan 0) gezet wordt, *N terug nul wordt, en *V vermenigvuldigd met x!. Het is echter moeilijk, zo niet onmogelijk, om effici¨ent te controleren wanneer de waarde van een expressie gewijzigd kan zijn, zeker in combinate met bv. pointers die kunnen wijzen naar geheugenplaatsen die buiten de controle van het programma zelf kunnen wijzigen (denk bijvoorbeeld aan Shared Memory (SHM) technieken waarbij een deel (virtueel) geheugen gedeeld kan worden tussen verschillende programmas).

Daarom wordt gesteld dat de CCHR programmeur zelf verantwoordelijk is voor aanduiden wanneer een expressie die als guard gebruikt wordt gewijzigd kan zijn. De syntax hiervoor wordt in sectie 6.4.3 aangereikt. Op zich is de programmeur niet verplicht voor deze reactivatie te zorgen, maar in dat geval verdwijnt de garantie dat het programma voldoet aan de verfijnde operationele semantief ωr.

6.3.3 Logische variabelen

Om de mogelijkheden van CHR in C niet te beperken, is er ook ondersteuning voor echte logische variabelen. Ze worden, echter algemeen voor C voorzien, buiten CCHR om. Ze zorgen dat logische built-in constraints gebruikt kunnen worden in CCHR. Dit houdt in:

• Een waarde geven (een logische variabele heeft niet noodzakelijk een waarde).

• De waarde opvragen

• Stellen dat een logische variabele gelijk is aan een andere logische variabele

• Controleren of 2 logische variabelen aan elkaar gelijk zijn

Logische variabelen hebben ook de mogelijkheid om door de programmeur gespecifieerde routines aan te roepen bij bepaalde acties. Dit kan het bekendmaken dat reactivatie nodig kan zijn aanzienlijk vereenvoudigen. Op logische variabelen wordt teruggekomen in sectie 7.5.2.

6.4 C routines

Tot hiertoe werden enkel de mogelijkheden beschreven die CCHR code biedt. Het is echter ook noodzakelijk te specifi¨eren hoe C code kan interageren met de CCHR constraints. Er wordt ingegaan op de C routines die ter beschikking gesteld worden. Deze routines zullen in praktijk C functies of macros zijn. Sommigen zijn algemeen voor een cchr-blok, en andere zijn specifiek voor bepaalde constraints.

6.4.1 Initializatie en terminatie

Vooraleer een CCHR constraint aan de constraint store mag toegevoegd worden, moet de store zelf ge¨ınitializeerd worden. Achteraf, wanneer geen gebruik van CCHR meer nodig zou zijn, is het mogelijk alle geheugen geassocieerd met CCHR (inclusief de constraint store en alle constraint suspensions die erin opgeslagen zitten) terug vrij te geven. Dit gebeurt met de routines:

cchr_runtime_init();

cchr_runtime_free();

(27)

6.4.2 Toevoegen van constraints

Vooraleer iets nuttig met CCHR gedaan kan worden, zal het nodig zijn tenminste ´e´en constraint aan de store toe te voegen. Om dit te doen wordt per constraint volgende functie voorzien:

void cchr_add_hconstrainti_hariteiti(harg1i,harg2i,...);

6.4.3 Reactivatie

Soms is het nodig de CCHR runtime te informeren dat de waarde van een expressie in een guard gewijzigd zou kunnen zijn. Hier worden volgende routines voor voorzien:

/* alle constraint suspensions */

cchr_reactivate_all();

/* alle constraint suspensions van bepaalde constraint */

cchr_reactivate_all_hconstrainti_hariteiti();

/* enkel bepaalde constraint suspension */

cchr_reactivate_hconstrainti_hariteiti(cchr_id_t PID);

6.4.4 Iteratie

Uiteindelijk moet het mogelijk zijn de inhoud van de constraint store op te vragen. Hiervoor is volgende routine voorzien:

cchr_consloop(hvari,hconstrainti_hariteiti,hcodei)

hcodei is hierbij een stuk arbitraire C code dat voor elke constraint van type hconstrainti en ariteit hariteiti doorlopen wordt. Binnen hcodei kan verwezen worden naar de argumenten van de betrokken constraint met behulp van:

cchr_consarg(hvari,hconstrainti_hariteiti,hnumi)

waarbij hnumi naar het argument nummer num verwijst (te beginnen tellen vanaf 1).

(28)

Hoofdstuk 7

Ontwerp en implementatie

In dit hoofdstuk wordt ingegaan op hoe ons CCHR systeem ontworpen en ge¨ımplementeerd is.

7.1 Algemeen

Zoals reeds aangehaald bestaat de implementatie van een CHR systeem normaal uit twee delen: de compiler en de runtime. De compiler zorgt voor een vertaling van de CHR-syntax naar code die uitvoerbaar is op het host-platform, en de runtime bevat alles wat noodzakelijk is om de vertaalde code te kunnen uitvoeren (algemene routines, onderhouden van de constraint store, . . . ).

Onze compiler is zelf in C geschreven en vertaalt CCHR code in enkele stappen tot normale C code, die dan verder door een standaard C compiler vertaald kan worden tot uitvoerbare code (machinetaal voor een specifiek platform). In tegenstelling tot Prolog en Java wordt het programma bij compilatie volledig tot machinetaal herleid, en is er dus geen interpretatie of Just-in-time compilatie meer nodig bij de uitvoering.

7.2 De Compiler

If the programmer can simulate a construct faster then the compiler can implement the construct itself, then the compiler writer has blown it badly.

- Guy L. Steele Jr., Tartan Laboratories

De compiler is het belangrijkste deel van het CCHR systeem. Het algemene concept is sterk gebaseerd op JCHR: de CHR broncode wordt naar de host-language zelf vertaald, die dan door de bestaande compilers voor die taal verder gecompileerd kan worden tot een echt uitvoerbaar programma. De compiler zelf begint met een parser en lexer om de taalstructuur van de CHR broncode te achterhalen, gevolgd door een omzetting naar een tussenvorm waarop enkele analyses gebeuren, en eindigt met een template-gebaseerde vertaling naar de uiteindelijke hosttaal. De precieze implementatie verschilt wel danig:

• De CCHR compiler vertaalt logischerwijs naar C en niet naar Java

• De CCHR compiler is zelf ook in C geschreven. De JCHR compiler was zelf in Java gemaakt.

(29)

• In plaats van een extern pakket voor de templates te gebruiken, worden standaard C macro’s gebruikt.

De grote fases zijn min of meer gescheiden van elkaar in de code. Ze zijn elk gedefinieerd in 1 of meerdere aparte bronbestanden, en de datastructuren die gebruikt worden voor de communicatie tussen de verschillende modules zijn op zich weer apart gedefinieerd. Zo zal de parser een Abstract Syntax Tree als resultaat geven, die enkel door de vertaal/analyse-module gebruikt wordt voor een omzetting naar een tussenvorm, waarop enkele statische analyses uitgevoerd kunnen worden, en die dan eenvoudig te gebruiken is door de codegeneratie om tot C macro’s te vertalen.

7.2.1 Algemeen

De algemene werking van de CCHR compiler is als volgt:

• Alle op de commandolijn opgegeven bestanden worden doorlopen, en letterlijk gekopi¨eerd naar de uitvoer (C).

• Als in een van de bestanden een cchr-blok gevonden wordt:

– De parser wordt aangeroepen met dat cchr-blok als invoer.

– De parser roept zelf de lexer aan om syntactische elementen te herkennen.

– De parser bouwt een abstract syntax tree (AST).

– De AST wordt geanalyseerd, en een tussenvorm wordt opgebouwd.

– Op de tussenvorm worden optimalisaties doorgevoerd.

– Uiteindelijk wordt de tussenvorm opgezet naar een sequentie van C macro’s.

– Deze C macro’s worden in het uitvoerbestand (C) op de plaats gezet waar het cchr-blok stond.

7.2.2 De lexer

De lexer is geschreven met behulp van Flex. Op de website van Flex staat te lezen:

Flex is a fast lexical analyser generator. It is a tool for generating programs that perform pattern-matching on text.

Op basis van een bestand met definities van patronen, in de vorm van regular expressions, kan Flex een C bronbestand genereren dat heel snel een stuk input kan splitsen in de opgegeven patronen.

(30)

Er kan opgemerkt worden dat de werkwijze van Bison sterk lijkt op de CCHR compiler zelf. Er wordt ook uitgegaan van een andere taal die in C ingebed kan worden, en met behulp van een template- gebaseerde methode wordt pure C code gegenereerd.

Hiervoor is de grammaticale structuur van CCHR beschreven als een Bison Context-Free Grammar, met semantische acties erbij die een AST genereren. Er moet wel opgemerkt worden dat hoewel CCHR toelaat arbitraire C code op te nemen, de CCHR grammatica geen volledige C grammatica bevat.

Ingebedde C code wordt namelijk niet volledig geparset, slechts tot op de hoogte dat noodzakelijk is om het begin en het einde ervan te herkennen. Dat wil bijvoorbeeld zeggen dat 1+2*(3-4) gewoonweg als 1 + 2 * ( 3 - 4 ) beschouwd wordt, en niet als +(1,*(2,-(3,4))). Het letterlijk doorgeven van C expressies volstaat, aangezien alles toch nog door de C compiler zelf moet.

Voor de tokens die de grammatica als basisblokken gebruikt, wordt beroep gedaan op de lexer.

Het resultaat hiervan is dus een AST, die echter helemaal niet geschikt is om converties en analyses op uit te voeren. Alle variabelen, constraints, . . . zijn nog steeds beschreven als een hoop tekenreeksen.

In de volgende stap wordt dit omgezet naar een werkbaar formaat.

7.2.4 De tussenvorm

Na deze stap wordt de AST omgezet naar een nieuwe datastructuur, waarbij constraints, variabelen, regels, . . . als aparte datastructuren in plaats van als tekenreeksen beschreven worden.

De reden om de parser niet onmiddellijk via semantische acties deze tussenvorm te laten genereren is meer vrijheid in de taal te kunnen toelaten. Zo is het nu bijvoorbeeld mogelijk om een constraint pas te defini¨eren nadat die in een CHR regel gebruikt is.

Tijdens de omzetting van AST naar deze tussenvorm worden volgende transformaties doorgevoerd:

• Alle verwijzingen naar constraints, variabelen, opties, . . . worden herkend.

• Alle regels worden omgezet naar HNF (Head Normal Form), waarbij alle expressies als parame- ters van contraints in de head die geen unieke variabelen zijn door een nieuwe variabele + een extra guard vervangen worden.

• Macros worden vervangen door hun definitie.

• Constraint occurrences worden bepaald (in welke rules en op welke plaats daarin elke constraint voorkomt).

• Variabele occurrences worden bepaald (in welke constraint occurrence en op welke plaats daarin elke variabele voorkomt).

• Afhankelijkheden tussen variabelen en statements worden bepaald.

• Met deze afhankelijkheden wordt voor elke constraint occurrence een goede “join ordering”

bepaald (zie sectie 7.3.5).

7.2.5 Code generatie

In de laatste fase van het vertalingsproces wordt de tussenvorm omgezet naar C code. Bij JCHR wordt van de template engine FreeMarker gebruik gemaakt. Het voordeel van templates gebruiken is

(31)

proces op hoger niveau. Implementatie details zoals datastructuren kunnen dan onafhankelijk van de compiler uitgewerkt worden, wat het geheel flexibeler maakt en de kans op fouter beperkt.

In C bestaat echter reeds een gestandaardiseerd macro-systeem. Er is dan ook voor gekozen om deze C macros te gebruiken in plaats van een apart template engine. Het programma dat de macro-vertalingen doet, de C preprocessor, is standaard deel van het compilatieschema van C, waardoor het overbodig is om in de CCHR compiler deze vertaling te doen.

Het resultaat is dat het volledige template-vertaalproces verschoven wordt van de CCHR compiler naar het C compilatie-schema, en de uitvoer van de CCHR compiler is een sequentie van C macros in plaats van echte C code.

Het voordeel hiervan is dat de uitvoer van de CCHR compiler heel leesbaar blijft, en onafhankelijk blijft van enkele details. Zo is het mogelijk om een debug-versie van het CCHR programmatie te cre¨eren zonder de CCHR compiler opnieuw te moeten uitvoeren, enkel het resultaat ervan hercompileren met de C compiler en een andere optie volstaat. Het belangrijkste nadeel is de moeilijkheden dat het veroorzaakt bij het debuggen.

De gegenereerde code wordt in detail uitgewerkt in sectie 7.3.

7.2.6 Uitvoer module

Uiteindelijk roept de codegenerator een uitvoer module aan, die verantwoordelijk is voor de code mooi ge¨ındenteerd weg te schrijven naar het uitvoerbestand, en ondertussen informatie bij te houden over het aantal geschreven regels. Dit is nodig omdat er lijnen van de vorm:

#line "source.cchr" 16 ...

#line "source.c" 214

in de uitvoer gezet worden, die de C compiler hints geven over waar de code in het bestand vandaan kwam, om zinvollere waarschuwingen te kunnen geven.

7.3 Gegenereerde code

Zoals gezegd bestaat de gegenereerde code uit C macros. In dit stuk wordt ingegaan op de structuur van die gegenereerde code. Eerst wordt een inleiding gegeven op de C voorvertaler, en dan wordt een voorbeeld stap voor stap uitgewerkt, om te eindigen bij de effectieve gegenereerde code die in de appendix te vinden is.

(32)

Het headerbestand stdio.h ingeladen worden. Volgens de standaard zal dit bestand definities opnemen voor een aantal datatypes en functies nodig voor invoer/uitvoerroutines.

Alle “instructies” die deze preprocessor kent heten directives (directieven), en moeten op een aparte lijn in het bronbestand staan, te beginnen met een hekje (#). De belangrijkste directives die wij gebruiken zijn #include, en #define. Dat laatste dient om een macro te defini¨eren.

Macros zijn tokens die gedefinieerd worden als te substitueren door een reeks andere tokens. De eenvoudigste vorm, ook objectvorm genaamd, is:

#define FOO bar(1);

wat aangeeft dat vanaf hier in de code “FOO” vervangen zal worden door “bar(1);”. Macros kunnen echter ook parameters aannemen, de functionele vorm:

#define FOO(par) bar(par1,par1+1);

waarbij bij voorbeeld de code “FOO(7)” vervangen zal worden door “bar(7,7+1);”. Zulke macros hebben ook ondersteuning voor variable arguments:

#define FOO(par,...) bar(par,__VA_ARGS__)

Hier zal “ VA ARGS ” de plaats innemen van alle argumenten die na par komen bij de vermelding van FOO. Zo zal bijvoorbeeld “FOO(sys,1,2)” vervangen worden door “bar(sys,1,2)”. De laatste mogelijkheid die gebruikt wordt is token pasting:

#define FOO_1(arg) run(arg)

#define FOO_2(arg) test(arg)

#define BAR(par,sys) FOO_##par(sys)

De ## zorgt hier dat 2 tokens aan elkaar geplakt worden, en onderwerpt het resultaat terug aan macro-expansie. Zo zal in bovenstaand voorbeeld “BAR(1,2)” vervangen worden door “run(2)”, maar “BAR(2,1)” door “test(1)”.

In de komende tekst zal vaak verwezen worden naar macros, sommigen daarvan staan in het macro- definitie bestand, anderen worden door de compiler gegenereerd. Gegenereerde macros zullen daarom ook steeds “gegenereerde macros” genoemd worden, om verwarring te vermijden.

7.3.2 Algemeen

Met het oog de gegenereerde macro-code niet te overladen met informatie die over heel het programma hetzelfde is, zoals de lijst van alle chr-constraints, is gekozen zoveel mogelijk op een algemene manier op te slagen. Dit vraagt een woordje uitleg.

Stel dat dit CCHR blok vertaald moet worden:

cchr {

constraint fib(int,long long),init(int);

(33)

alt(N2==N1+1,N2-1==N1), N2<Max | fib(N2+1, M1+M2);

}

De volledige compiler uitvoer kan u vinden in sectie A.1.

De eerste belangrijke lijn die gegenereerd wordt is deze:

#define CONSLIST(CB) CB##_D(fib_2) CB##_S CB##_D(init_1)

Deze lijn defini¨eert welke chr constraints allemaal bestaan. Ze is heel flexibel in gebruik, de aanroeper moet zelf 2 macros of functies voorzien: een voor het defini¨eren van een constraint, en een voor wat er tussen 2 constraints moet gebeuren. Zo is het mogelijk om code te laten genereren voor elke constraint, gescheiden met comma’s, mits:

#define CB_D(con) CODE_VOOR_CONSTRAINT(con)

#define CB_S , CONSLIST(CB)

Deze techniek wordt echter voor een stuk meer gebruikt dan enkel het aanduiden van de bestaande chr constraints. Er worden zulke index-macros gedefinieerd voor:

• Welke chr constraints bestaan (CONSLIST).

• De occurrences van elke constraint (RULELIST hconstrainti hariteiti).

• Aantal kept/removed constraints in elke rule (RULE KEPT hrulei).

• Wat en waarin in propagation history bij te houden (PROPHIST hrulei en PROPHIST HOOK hrulei).

• Welke indexen nodig zijn, en waarover (HASHLIST hconstrainti hariteiti en HASHDEF hconstrainti hariteiti hindexnaami).

Verder worden er nog gelijkaardige, maar eenvoudigere constructies gegenereerd voor constructor-, destructor-, add en kill routines per chr constraint.

Dan volgen nog enkele macros die de het mechanisme op hoog niveau beschrijven wat er voor elke constraint occurrence moet gebeuren. Hier wordt zodadelijk op ingegaan.

Als afsluiter van de gegenereerde code staat een “CSM START”, deze macro is gedefinieerd in het al- gemene macro-definitie bestand (zie runtime), en zal gebruikmakende van alle eerder gegenereerde

(34)

“hconstrainti hariteiti hrulei hpositiei”. Positie is hierbij de letter K voor kept constraints, of de letter R voor removed constraints, gevolgd door een getal dat aanduidt de hoeveelste occurrence van dat type (removed of kept) het is binnen de gegeven rule, te beginnen bij 1.

Dan de inhoud van deze macros. Het is hier dat het voordeel van een template-gebaseerde code generatie tot uiting komt: het algorithme wordt niet als C code gegenereerd, maar als een sequentie van macros. Deze set van macros is CSM gedoopt (Constraint Solver Macros), en een volledige lijst beschikbare macros kan u vinden in appendix B. Het kan nuttig zijn de lijst erbij te nemen bij de komende uitleg, aangezien de precieze betekenis van de CSM macros hier niet meer uitgelegd wordt.

Als eerste versie wordt vertrokken van een imperatieve versie van de basisuitvoering, zoals beschreven in [Sch05]:

• Eerst ervoor zorgen dat de actieve constraint bestaat (CSM MAKE), en toegevoegd is aan de con- straint store (CSM NEEDSELF).

• Er wordt ge¨ıtereerd over alle partner constraints, zijnde de constraint occurrences in de huidige rule behalve de actieve, mbv. CSM LOOP.

• Er wordt controleerd of er geen dubbels zijn in de verschillende partner constraints (mbv.

CSM DIFF en CSM DIFFSELF binnen een CSM IF).

• Er wordt controleerd of de gevonden combinatie nog niet reeds geprobeerd is (mbv. CSM CHECKHIST).

• Alle lokale variabelen worden gedefinieerd, met CSM DECLOCAL en CSM DEFLOCAL.

• Er wordt gecontroleerd of aan alle guards voldaan is (mbv. CSM IF en de constraint argumenten met CSM ARG en CSM LARG geschreven).

• De gevonden combinatie wordt toegevoegd aan de propagation history (mbv. CSM HISTADD).

• Eventuele removed constraints worden uit de constraint store verwijderd (mbv. CSM KILL en CSM KILLSELF).

• De body van de CHR rule wordt uitgevoerd, met de verwijzingen naar lokale variabelen vervangen door CSM LOCAL-macros en de constraint argumenten door CSM ARG en CSM LARG.

• Als de actieve constraint uit de constraint store verwijderd is, wordt de afhandeling gestopt (CSM DEADSELF en CSM END).

Met deze versie zijn enkele problemen, die misschien niet op het eerste zicht duidelijk zijn:

• Zodra een CSM KILL gebeurd is, kan CSM LARG niet meer gebruikt worden, omdat naar een onbestaande constraint verwezen kan worden (of erger nog: naar een andere constraint suspen- sion die nu op die plaats staat). Daarom zullen de constraint argumenten voor de uitvoering van de body opslagen worden in lokale (onwijzigbare) variabelen, mbv. CSM IMMLOCAL. Als er dan verder naar verwezen wordt in de body, wordt CSM LOCAL ipv. CSM LARG gebruikt om ernaar te verwijzen.

• Het is mogelijk dat een constraint-argument verwijst naar een apart gealloceerd object, dat dmv.

een destructor aangegeven is voor vernietiging bij verwijderen van de constraint suspension. Deze destructor kan echter niet aangeroepen worden door CSM KILL, omdat code verder in de body misschien nog naar het apart gealloceerd object kan verwijzen. Daarom wordt een CSM DESTRUCT ingevoerd voor elke CSM KILL of CSM KILLSELF, die pas na de body aangeroepen wordt.

(35)

• Wanneer de actieve constraint nog niet uit de constraint store verwijderd is, maar een van de voorgaande partner constraints al wel, dan moet onmiddellijk het volgende element van die iterator voor die partner constraint gekozen worden, om te vermijden dat de body toegepast zou worden voor een op dat moment niet meer bestaande constraint suspension. Daarom wordt na de controle of de actieve constraint nog in de store zit, ook een controle ingevoerd voor alle CSM LOOPs, van buiten naar binnen, dmv. CSM DEAD, met een CSM LOOPNEXT op de betrokken variabele in. Deze controle is echter niet nodig voor de binnenste lus, aangezien daar sowieso onmiddellijk het volgende element gekozen zal worden.

7.3.4 Optimalisaties

Deze eerste versie van het compilatieschema is echter voor verbetering vatbaar. Voor een gedetailleerde uitleg over de optimalisaties en waarom ze toegestaan zijn, wordt verwezen naar [Sch05].

Dubbels per type: Het is enkel nodig om te controleren op dubbele constraint suspensions (CSM DIFF en CSM DIFFSELF) tussen suspensions van hetzelfde constraint type.

Propagation history: Men hoeft enkel een propagation history bij te houden voor rules die geen removed constraints hebben. Deze laatste kunnen immers sowieso geen 2 maal uitgevoerd worden.

Destruction: Indien een destructor aangeroepen moet worden voor een bepaalde constraint suspen- sion, is het niet nodig hiermee te wachten tot na de uitvoering van de gehele body. Dit kan (meestal) gedaan worden zodra niet meer naar een variabele van die constraint verwezen wordt in de rule. De CCHR compiler zal de CSM DESTRUCT macro dan ook plaatsen na het laatste stuk body dat naar een variabele ervan verwijst.

Late Storage: Het is wenselijk om het aanmaken van een constraint suspension, en zeker het eigenlijke toevoegen ervan aan de constraint store, zoveel mogelijk uit te stellen. Dit eerste bespaart geheugen, en dit tweede kan de snelheid ten goede komen, aangezien er ondertussen minder elementen in de store zijn om over te itereren. Er wordt voor gezorgd dat de suspension aangemaakt is juist voor het zoeken naar partner constraints van een propagate-overgang1, door de CSM MAKE enkel te plaatsen aan het begin van “kept” occurrence macros. Verder wordt er pas voor gezorgd dat de constraint suspension in de store zit aan het begin van de uitvoering van de body van zo’n propagate-overgang.

De CSM NEEDSELF wordt dus geplaatst voor de body van zo’n occurrence.

Simplification: Bij simplificatie-overgangen is er zekerheid dat na uitvoering van de body, de actieve constraint zich niet meer in de store zal bevinden. Er is dus geen CSM DEADSELF meer nodig om te controleren of een CSM END mag. Dit kan uitgebreid worden tot propagation-overgangen die partner constraints verwijderen. Hierbij kan de CSM DEAD conditie rond de CSM LOOPNEXT weggelaten worden.

Daarbij komt nog dat na een dergelijke niet-conditionele CSM END of CSM LOOPNEXT geen verdere checks voor de diepere lussen meer nodig zijn, en dus volledig weggelaten kunnen worden. Het gebruik van deze expliciete CSM LOOPNEXT macros komt overeen met wat in JCHR “backjumping” genoemd wordt.

(36)

7.3.5 Join ordering

It is in justice that the ordering of society is centered.

- Aristotle (384 BC - 322 BC)

Een meer algemene verbetering die aangebracht kan worden aan voorgaand compilatieschema, is wat men “join ordering” noemt, zoals vermeld in [HGSD05]. Tot hiertoe werd de volgorde waarin ge¨ıtereerd wordt over de verschillende partner constraints ongedefinieerd gelaten, maar een juiste keuze kan veel versnelling bij de uitvoering mogelijk maken.

In het algemeen komt het neer op zoveel mogelijk statements en voorwaardes die gecontroleerd worden voor de uitvoering van de eigenlijke body, zo snel mogelijk te doen, dwz. binnen zo weinig mogelijk lussen. Een overzicht:

• Guards (ook impliciete, door HNF convertie)

• Controles op dubbele constraints

• Propagation-history controles

• Definities van lokale variabelen

• Lokale statements in de guard, die tot hiertoe niet vermeld waren.

In bovenstaand compilatieschema gebeuren al deze dingen pas zodra over alle partner constraints gelopen is, terwijl heel wat mogelijkheden al op voorhand uitgesloten zouden kunnen worden. Daarom wordt geprobeerd zoveel mogelijk van deze dingen reeds tussen de lussen door te controleren. Ze kunnen echter afhankelijk zijn van constraints, of van contraint-argumenten en lokale variabelen, die zelf ook van eerder gedefinieerde dingen afhankelijk kunnen zijn.

Hiervoor wordt volgend algoritme in de compiler gebruikt:

• Er wordt ge¨ıtereerd over alle mogelijke volgordes van iteratie (O(N !) combinaties, met N het aantal partner constraints).

• Voor elke volgorde wordt geprobeerd elke controle of statement zo vroeg mogelijk te plaatsen.

• Er wordt een “score” berekend, die aangeeft hoe snel deze volgorde verwacht wordt te zijn. Deze wordt bepaald door aan alle acties een gewicht toe te kennen, en deze te vermenigvuldigen met een factor voor elke lus waarbinnen ze staat.

• Uiteindelijk de volgorde met de laagste score te kiezen.

De gewichten die gebruikt worden zijn redelijk eenvoudig, en komen vermoedelijk niet overeen met de de realiteit. De bekomen volgorde zal daardoor vaak niet optimaal zijn, maar het verschil is wel aanzienlijk.

7.3.6 Indexen

If you don’t find it in the index, look very carefully through the entire catalogue.

(37)

Na al deze verbeteringen blijft het meeste tijd verloren gaan in het opzoeken van de partner constraints.

In sommige gevallen kunnen de nodige partner constraints al bekend zijn, door indexen aan te leggen en te onderhouden die aangeven welke constraint suspensions allemaal een of meerdere waardes als bepaalde argumenten hebben. Hoe dit ge¨ımplementeerd is wordt uitgelegd in sectie 7.5.

Om deze indexen te gebruiken in CSM, is het nodig ze eerst in een index-macro te defini¨eren zoals aangehaald in sectie 7.1. Daarna is het ook nodig om in plaats van de traditionele CSM LOOP macro enkele andere macros te gebruiken. Eerst en vooral moet gedeclareerd worden dat een bepaalde variabele als index-iterator gebruikt gaat worden (met CSM DEFIDXVAR). Daarna moeten de op te zoeken waardes voor de verschillende argumenten ingevuld worden met CSM SETIDXVAR, en uiteindelijk moet ge¨ıtereerd worden met CSM IDXLOOP of CSM IDXUNILOOP. Op het verschil tussen beide wordt dadelijk ingegaan.

Deze optimalisatie is degene die de belangrijkste snelheidsverbetering teweegbracht bij de implemen- tatie, wat logisch is, bij sommige problemen maakt het de uitvoering een grootte-orde sneller door onmiddellijk de juiste constraint te vinden, in plaats van mogelijks er duizenden te moeten doorlopen.

Dit is ge¨ımplementeerd door bepaalde guards als speciaal te herkennen en aanleiding te laten geven tot indexen. Hoe dit gebeurt wordt uitgelegd in sectie 7.3.8.

7.3.7 Existenti¨ele en universele iteratoren

Tijdens het uitvoeren van CCHR code wordt er ge¨ıtereerd over constraints in verschillende geneste lussen, en middenin die lussen worden er constraints verwijderd, toegevoegd, en gereactiveerd. Dit zorgt ervoor dat de toestand van de constraint store, en indien er indexen gebruikt worden, ook deze indexen tijdens het itereren op allerlei mogelijke manieren kunnen veranderen.

Iteratoren die bestand zijn tegen willekeurige wijzigingen en garanderen dat elk element dat uiteindelijk in de store zit ook effectief doorlopen is, zijn heel moeilijk effici¨ent te schrijven. Er wordt dan ook niet ge¨eist dat CSM LOOP en consoorten deze functionaliteit aanbieden. Het is echter wel zo, dat constraint suspensions die tijdens het itereren toegevoegd worden aan de store, niet hoeven te worden gecontroleerd als mogelijke partner constraints, en dus niet noodzakelijk moeten overlopen worden door de iteratoren. Ze zijn immers reeds geactiveerd toen ze zelf toegevoegd werden, en ze zouden dus ondertussen reeds alle mogelijke transities met de huidige actieve constraint als partner constraint reeds moeten hebben doorgaan. Dit vergemakkelijkt de zaak duidelijk.

Het probleem dat onze iteratoren wijzigingen in de constraint store moeten aankunnen tijdens het itereren blijft echter. Blijkt echter dat dit niet altijd nodig is. Indien de simplification optimalisatie en expliciete backjumping ge¨ımplementeerd zijn, blijkt dat wanneer een rule minstens 1 removed con- straint bevat (eventueel de actieve constraint), er na het toepassen van de body van een regel sowieso wordt gesprongen naar de volgende waarde voor de “meest buitengelegen” iterator voor een removed constraint, of verder. Hieruit volgt dat de lussen die daarbinnen zitten nooit moeten verderlopen eens

(38)

7.3.8 Matching

Zoals aangehaald is het nodig om bepaalde guards te laten herkennen als efficienter te schrijven dan een simpele CSM IF. Het gaat hier om controles die ingebed kunnen worden in een lus. Daarom is een algmene routine geschreven die gegeven de nog te controleren guards op een bepaalde plaats in het uitvoeringsalgoritme, beslist welke soort iterator gebruikt moet worden. De verschillende iteratoren hebben een prioriteit toegekend gekregen, en voor elke iteratie die moet gebeuren wordt de iterator met de hoogst mogelijke prioriteit gekozen.

Er zijn 3 iteratoren ge¨ımplementeerd, van lage naar hoge prioriteit:

1. De simpele CSM LOOP iterator. Deze wordt enkel gekozen als er geen betere iteratoren gevonden kunnen worden. Deze iterator heeft geen impliciete guard.

2. De CSM IDXLOOP iterator die een waarde-index gebruikt voor het itereren. Alle guards die equiv- alenties voorstellen tussen een variabele die het argument is van eenzelfde constraint occur- rence, worden gebruikt om 1 index-iterator te bouwen. Op die manier kunnen een heel aantal guards samen in 1 iterator terecht komen. Bij de herkenning van dergelijke equivalenties, is de standaard C equivalentie (==) ondersteund, en ook een binaire equivalentie die algemener is (eq(arg1,arg2), zie sectie 6.2.4).

3. Iteratoren over de elementen van een index in een logische variabele bijgehouden. Dit is slechts half afgewerkt, maar een voorbeeld kan gevonden worden in sectie A.2.2.

Bij deze matching komt trouwens het sleutelwoord alt terug, dat in sectie 6.2.4 aangehaald werd.

Alle argumenten van een alt worden door het matchalgoritme overlopen, om een zo goed mogelijke match te vinden.

7.4 CSM Definities

Bij SWI-Prolog en JCHR kon een duidelijk onderscheid gemaakt worden tussen gegenereerde code en runtime. Bij CCHR is het echter de vraag of de definities van de CSM macros tot runtime gerekend kunnen worden. Het is namelijk geen code die tijdens de uitvoering nodig is, maar code die nodig is om de gegenereerde code naar een uitvoerbaar bestand te kunnen omzetten. Daarom zullen de de daarin behandelde problemen hier in een aparte sectie besproken worden.

De belangrijkste taak van CSM is de gebruikte datastructuren afschermen van de gegenereerde code, zodat deze beide onafhankelijk gewijzigd kunnen worden. Eerst wordt ingegaan op de gebruikte datastructuren, en dan de implementatie ervan zelf besproken.

7.4.1 Noodzakelijke datastructuren

Constraint store De belangrijkste datastructuur is uiteraard degene voor de constraint store. Het moet een structuur zijn die toelaat heel snel constraints toe te voegen, te verwijderen en erover te itereren. De volgorde waarin is in principe niet zo van belang, maar het niet-teruggeven van elementen die tijdens het itereren zelf zijn toegevoegd is een pluspunt. Dynamische allocaties tijdens de uitvoering blijven liefst zo veel mogelijk beperkt.

Propagation history Er moet ook een propagation history bijgehouden worden. Deze moet toelaten

(39)

Indexen Er is een datastructuur nodig om de indexen in bij te houden. Deze moet voor 1 of meerdere argumenten van een bepaalde constraint voor elke (combinatie van) waardes voor deze argumenten een lijst kunnen bijhouden van constraint suspensions die deze (combinatie van) waardes als argumenten heeft.

7.4.2 De constraint store

Voor de constraint store wordt gebruikt gemaakt van een structuur van doubly-linked lists. De juiste implementatie wordt uitgewerkt in sectie 7.5.1, en hier is het voldoende te weten dat het de mogeli- jkheid geeft om geheugenblokken van vaste grootte snel te alloceren en vrij te geven. Er kan een op voorhand bepaald aantal lijsten aan gekoppeld zijn, en elk blok kan in maximaal 1 zulke lijst geplaatst worden. Er wordt naar een blok verwezen dmv. een PID (positie ID), maar dit kan herbruikt worden nadat een blok vrijgegeven is. Verder krijgt elk blok een uniek ID, dat nooit herbruikt wordt.

Als datablok worden de constraint suspensions gebruikt. Deze is beschreven als een C union, een structuur die juist 1 van zijn elementen kan bevatten (door ze dezelfde geheugenruimte te laten delen), dit in tegenstelling tot een struct, die ze allemaal tegelijk bevat (na elkaar). Als elementen van die union worden de datastructuren voor elk van de verschillende types constraints gebruikt. Dit heeft als nadeel dat er wat plaats verpild wordt als de gegevens voor de verschillende types niet evenveel plaats innemen. Dit verschil is echter meestal beperkt. Een alternatief zou kunnen zijn een apart datablok gebruiken voor elk van de types, wat dan weer het nadeel heeft van meer allocaties tijdens het uitvoeren.

De lijsten die het doubly-linked list type aanbiedt, bevatten alle constraints van een bepaald type, en de CSM LOOP macro gebruikt dan de iterator van de linked list om erover te itereren. Het cchr id t datatype dat gebruikt wordt voor iteratoren, is dan ook gedefinieerd in het CSM definitiebestand als hetzelfde type dat de doubly-linked list implementatie gebruikt als PID. Op die manier zou men kunnen argumenteren dat de gegenereerde code toegang krijgt tot een definitie die door CSM afgeschermd zou moeten zijn. De gegenereerde code gebruikt echter het cchr id t type zelf niet, krijgt het enkel en geeft het door, en moet het verder als “black box” type beschouwen.

De constraint suspensions zelf bevatten (afhankelijk van het type):

• De argumenten van de betrokken constraint

• Eventuele propagation history (zie verder)

• Een generatienummer. Dit wordt verhoogd elke keer dat een constraint gereactiveerd wordt, om zo de generation optimalisatie te kunnen doorvoeren. Hierbij wordt het generatienummer van de constraint bij activatie bijgehouden, en zal de CSM DEADSELF macro ook “waar” teruggeven indien dat getal ondertussen veranderde.

(40)

7.4.3 De propagation history

History repeats itself; that’s one of the things that’s wrong with history.

- Clarence Darrow (1857 - 1938)

Om de propagation history bij te houden wordt een zelf ge¨ımplementeerde hashtable gebruikt. Op de implementatie ervan wordt ingegaan in sectie 7.5.3. Ze vereist dat zelf een datatype aangegeven wordt om in de hashtable te gebruiken, inclusief een manier om aan te geven of zulk een datatype een

“gebruikte plaats” voorstelt.

Voor de propagation history wordt dus de IDs (niet de PIDs) van de betrokken constraint suspensions opgeslagen, voorafgegaan door een 0 of een 1, die aangeeft of de plaats in gebruik is. Ze zijn twee opties:

1. Alles opslagen in 1 globale hashtable.

2. Voor elke rule R waarvoor propagation history bijgehouden wordt (geen simplification of sim- pagation rules dus) een van zijn constraint occurrence O van constraint type C kiezen, en in de constraint suspensions S van type C de elementen van de propagation history opslagen voor rule R die voor occurrence O de suspension S hebben.

Die tweede methode vraagt meer overhead (veel aparte hashtables in plaats van 1 grote), maar zorgt ervoor dat er niet expliciet elementen uit de propagation history verwijderd moeten worden. Deze verdwijnen immers automatisch zodra de constraint suspension waarin ze opgeslagen zitten verdwijnt.

7.4.4 De index

Voor de waardenindexen, die gebruikt worden met de CSM IDXLOOP en verwante macros, wordt opnieuw gebruik gemaakt van de hashtable implementatie die ook voor de propagation history gebruikt werd.

Ze wordt zelfs 2 maal in elkaar gebruikt: eerst een table om de verschillende waardes aan subtables te koppelen, en dan in die tables dan de betrokken constraint suspensions opslagen. Zowel de IDs als de PIDs op worden echter opgeslagen, waarbij de IDs (omwille van uniek zijn) als sleutel in de hashtable gebruikt worden, terwijl de PIDs als waarbij gebruikt worden, om eens een bepaalde constraint gevonden, ze ook snel terug te vinden. Dat laatste was bij de propagation history niet nodig, aangezien enkel snel kunnen controleren of een bepaalde (combinatie van) constraint suspension(s) aanwezig was vereist is, en niet de constraint suspensions zelf moeten kunnen terugvinden.

Voor CSM IDXLOOP wordt ge¨ıtereerd over de verschillende elementen, terwijl er voor CSM IDXUNILOOP eerst een kopie gemaakt wordt van de subtable, waar dan over ge¨ıtereerd wordt. Dit is nodig, omdat de hashtable implementatie geen wijzigingen tijdens het itereren toelaat. Zie sectie 7.5.3.

CSM zal automatisch alle indexen onderhouden die met de macro HASHLIST hconstrainti hariteiti gedefinieerd zijn, zowel het toevoegen van nieuwe waardes, nieuwe constraint suspensions bij de sub- table voor een bestaande waarde toevoegen, het verwijderen van constraint suspensions uit de juiste subtable, en indien er voor een gegeven combinatie van waardes geen overeenkomstige suspensions meer zijn, het verwijderen van de volledige subtable voor die waardes.

7.5 Runtime

Referenties

GERELATEERDE DOCUMENTEN

Twee parallelle rijen van telkens vier paalsporen vormden een iets groter bijgebouw dat eveneens een NW-ZO-oriëntatie ver- toont (fig. Deze structuur meet 3,5 bij 6 m, hoewel niet

It is not well understood how frequently pregnant and post- partum women become colonized and infected with ESBL-E, which risk factors promote ESBL-E colonization/infection, and

(Bron: Stadsarchief Sint-Truiden). Het kasteel van Ordingen was een voormalige waterburcht, opgebouwd volgens de klassieke opperhof- en neerhofstructuur. De plaats

Gebruikt de gegevens gegenereerd door analyser Genereert een aantal macro’s voor elke constraint/regel Voor elke constraint occurrence een macro met code.. 1 Algemeen Waarom

Gebruikt de gegevens gegenereerd door analyser Genereert een aantal macro’s voor elke constraint/regel Voor elke constraint occurrence een macro met code..

20.. If agenda is empty, then go to choice, else remove a constraint rule from the agenda. If the rule fires and succeeds, i.e., a variable is added to ground, go to shedule; else

McPal ( Evol ): MigrPhase migrDone → StatPhase, McPal [ Crs : = Crs toBe ] The first new rule says, on the basis of having entered trap ready, the phase change from StatPhase

The prototype is a direct implementation of the algorithm described in the previous section. The interface, shown in Fig. 5, consists of a text box to type the constraint