• No results found

5.4.3 L1 - Exekvering

Instruktionen call sätter helt enkelt en minnesadress vars instruktion ska exekveras härnäst och proceed gör än så länge ingenting, se tabell 5.8.

Instruktion Beskrivning

call <addr> Hoppa till instruktion på adress <addr> proceed Programmet lyckades!

Tabell 5.8. Nya instruktioner i L1 för CODE

Adressen som call anropar tas redan på vid kompileringen, så om förfrågan använder sig av ett predikatnamn som inte finns i programmet så vet man redan där att det kommer att misslyckas. Alternativt skulle man kunna ha en dynamisk uppslagning av adressen vid varje anrop och därmed tillåta att man bytte ut fakta i programmet (Detta kommer att behövas senare för att hantera GDL:s true och does, men för övriga predikat kommer man att länka statiskt av prestandaskäl).

Att misslyckas med ett anrop är inte ett större fel än att misslyckas med att matcha en term. (Jämför t.ex. om man ska matcha ett faktum).

De nya L1-instruktionerna för A-register exekverar bara i read-mode för pro-gram. Se figur 5.6 för ett exempel på exekvering av förfrågan mot program i L1.

5.4.4 L1 - Hämta resultat

I L1 lagras inte längre predikatet på HEAP, utan det får man komma ihåg från sin förfrågan tillsamman med den ställighet den hade. Predikatets subtermer ligger lagrade i ordning i A-registren, man kan hämta ut var och en av dessa på samma sätt som man gjorde för hela predikatet i L0.

5.5 L2 - Regler

5.5.1 L2 - Grammatik

Språket L2 tillåter inte bara fakta i programmet utan även satser. Man har dock fortfarande kvar begränsningen att det inte får finnas mer än ett predikat i varje relation.

Man tillåter också att förfrågningen kan bestå av flera delmål. ; Everything else is defined as before.

query :

(literal)+ DOT!; program :

(clause)+; clause :

predicate | constant_predicate | rule; rule:

LPAREN! i:IMPLICATION^ (predicate | constant_predicate) (literal)+ RPAREN!

KAPITEL 5. KOMPILATORN

Figur 5.6. Exempel på exekvering av förfrågan (f ?x (g ?x a)) mot program som

5.5. L2 - REGLER ; Exempel på L2-program (<= (p a ?b) (c a) (d ?b))

5.5.2 L2 - Kompilering och länkning

Eftersom satser är sanna om deras kropp är sann så fungerar kroppen som en förfrågan. Därför kompileras bara huvudet som ett program, kroppen kompileras precis som en förfrågan. Man allokerar dock först registren för hela satsen på en gång, eftersom dessa ska vara gemensamma över alla predikat.

Nu måste man först kompilera och sedan länka koden i separata steg, eftersom man tillåter att satser definierade tidigare i programmet anropar satser som man inte stött på ännu och vars adresser därmed inte är kända ännu. I kompileringen läggs id:t till predikatet in i call-funktionerna, inte dess adress. Under länkningen byts sedan id:t ut mot den riktiga adressen.

När man kompilerar kod för en kropp eller en förfrågan som består av flera predikat kompilerar man predikaten i tur och ordning och lägger in deras genererade kod i samma ordning i programmet.

Kompileringen går ungefär till på samma sätt som i tidigare språk förutom två viktiga skillnader; man måste hålla reda på var i ett delmål man befann sig när man gör ett anrop eftersom unifieringen kan gå i flera steg och man kan behöva skydda registren över predikatanrop, då de kan behövas både före och efter ett predikatanrop som i sin tur kanske använder sig av samma register.

Registerallokering, Y register Man måste i L2 börja ta hänsyn till att variabler kan leva över flera proceduranrop:

p(X, Y) :-q(X, Z), r(Z,Y)

I ovanstående fall skulle man få problem om man lade in Y och Z i vanliga X-register, eftersom man inte vet vilka register som q och r använder sig av. Man behöver alltså skydda dessa variabler. X behöver däremot inte skyddas eftersom variabeln inte används efter ett anrop till ett annat predikat, det spelar ingen roll om dess register blir överskrivet.

De variabler som behöver skyddas, kallas permanenta och lagras i en särskild uppsättning register som kallas Y-register, som behandlas på ett särskilt sätt vid funktionsanrop.

Man använder samma instruktioner mot Y-register som mot X-register, det enda som skiljer sig är hur man räknar ut vilken minnescell som ska läsas eller skrivas.

Nya instruktioner Den enda nya instruktioner som behövs är allocate och deallocate. De läggs som första resp. sista instruktion i varje regel (om man inte använder sig av LCO optimeringen, se 5.5.4). Se avsnittet om STACK för en närmare beskrivning av vad de gör.

KAPITEL 5. KOMPILATORN

5.5.3 L2 - Exekvering

CP pekaren Till skillnad från L1 så är det i L2 inte säkert att man är klar med unifieringen när man når slutet på ett predikat, eftersom satser består av flera delmål. Efter att man lyckas unifiera ett delmål med ett predikat i programmet måste man på något sätt veta var man ska returnera till. Detta lagras i ett särskilt register CP som pekar till en position i CODE arean. Se tabell 5.9 för tillägg i instruktioners utförande.

Instruktion Utförande

call CP = P + 1

proceed P = CP

Tabell 5.9. Tillägg i instruktioner i L2

Detta fungerar rakt av så länge man bara anropar fakta. Anropar man en regel så kommer den att skriva över det nuvarande värdet på CP, så då är man tvungen att skydda det registret.

STACK, E pekare För skydda de permanenta variablerna vid proceduranrop introducerar man en ny datastruktur som kallas för STACK. Den fungerar ungefär som stacken i processorer i det att man bara kan allokera / deallokera i toppen på stacken, men man kan läsa och skriva på godtycklig position i den.

När man gör ett “proceduranrop”, d.v.s. när man matchar en av literalerna i kroppen, sparar man en mängd information, en environment frame, på stacken som sedan återställs då anropet returnerar, se figur 5.7. Man lägger också till ett nytt register, E pekaren, som anger var på stacken den senast lagrade enviroment frame:n ligger. Informationen som lagras på stacken innehåller CP pekaren, Y-registren och en pekare till föregående enviroment frame. Notera att Y-registren från början till slut ligger på stacken. Det är alltså inte så att de sparas undan när man gör ett anrop, utan de är egentligen alias till en mängd positioner på stacken.

CP E Y1 n=Antalet Y-Register ... Yn

Figur 5.7. Environment Frame

Instruktionen allocate är den första som exekveras då man kommer in i en regel, och deallocate den sista. Detta är för att spara undan de permanenta register som kan finnas i regeln. Har man inga permanenta register lägger man fortfarande in allocate / deallocate för att spara undan CP.

I övrigt körs program som på samma sätt som i L1. Dock dyker nu problemet upp med oändlig rekursion:

(<= (p ?a) (p ?a))

Detta är ett giltigt L2 program, som kommer att stanna först när STACK tar slut.

5.5. L2 - REGLER

5.5.4 Optimeringar - Chain rule och LCO

En observation man kan göra är att de permanenta variablerna i en enviroment frame inte behövs över det sista anropet. De kopieras ju in i argumentregistren och behövs inte mer i satsen sedan. Man kan alltså redan innan det sista anropet deallokera enviroment fram med deallocate, och på så sätt vinner man lite stack-utrymme. Framförallt kan detta göra skillnad om det sista anropet är rekursivt, då man gång på gång återanvänder samma utrymme på STACK. På sätt och vis förvandlar man då det rekursiva predikatet till ett “iterativt predikat”. Detta kallas för “Last call optimization” (LCO).

För att göra denna optimering modifierar man deallocate så att den uppdaterar CP istället för P och lägger till en ny funktion, execute.

Ett särskilt fall av detta är en så kallad “chain-rule”, där kroppen bara består av ett anrop. I detta fall har man ju inga permanenta variabler, och eftersom man använder sig av execute för det enda anropet så rör man aldrig CP. Detta innebär att man helt kan skippa både allocate och deallocate.

Program: (<= (f3 ?a ?b) (g ?a) (h ?b)) (g a) (<= (h ?b) (i ?b)) (i b) Query (f3 a b). 125 : allocate 0 126 : put_constant c15, A1 127 : put_constant c5, A2 128 : call L8, 2 f3/2 - sats 8 : allocate 1 9 : get_variable X3, A1 10 : get_variable Y1, A2 11 : put_value X3, A1 12 : call L89, 1 g/1 - Rent faktum 89 : get_constant c15, A1 90 : proceed Tillbaka i f3/2 13 : put_value Y1, A1 14 : deallocate 15 : execute L54, 1

KAPITEL 5. KOMPILATORN

h/1 - Chain rule

54 : get_variable X2, A1 55 : put_value X2, A1 56 : execute L91, 1

i/1 Rent faktum

91 : get_constant c5, A1 92 : proceed

Tillbaka i förfrågan 129 : done

5.5.5 L2 - Hämta Resultat

Nu kan man inte längre få ut resultatet genom att man kollar på HEAP. HEAP innehåller bara matchningen av det sista delmålet. Vad man gör nu istället är att sätta varje variabel i förfrågan som permanent, och då kan man hämta ut innehållet från Y-registren. Detta innebär att man måste hålla redan på vilken variabel som hörde ihop med vilket Y-register. Dessutom kommer resten av förfrågan, d.v.s. allt som inte är variabler, inte vara tillgänglig inne i VM, utan får lov att sparas undan utanför den.

Detta förfarande innebär också att man inte kan använda sig av execute som sista instruktion i en förfrågan som man vill ha ut resultatet ifrån, eftersom man behöver ha de permanenta registren intakta även efter sista anropet. I en förfrågan byts execute ut mot en call istället.

Related documents