• No results found

Partialevaluering : Specialisering av program  Utformning av en laboration

N/A
N/A
Protected

Academic year: 2021

Share "Partialevaluering : Specialisering av program  Utformning av en laboration"

Copied!
79
0
0

Loading.... (view fulltext now)

Full text

(1)

Institutionen för datavetenskap

Department of Computer and Information Science

Examensarbete

Partialevaluering - Specialisering av program.

Utformning av en laboration

av

Alexander Binggeli

LIU-IDA/LITH-EX-G--11/030--SE

2012-01-15

(2)

Linköpings universitet Institutionen för datavetenskap

Examensarbete

Partialevaluering - Specialisering av program.

Utformning av en laboration

av

Alexander Binggeli

LIU-IDA/LITH-EX-G--11/030--SE

2012-01-15

Handledare: Anders Haraldsson

Examinator: Peter Dalenius

(3)

ISBN (licentiatavhandling) ISRN LIU-IDA/LITH-EX-G--11/030--SE Serietitel (licentiatavhandling) Serienummer/ISSN (licentiatavhandling) Typ av publikation Licentiatavhandling X Examensarbete C-uppsats D-uppsats Rapport

Annat (ange nedan)

Språk X Svenska Annat(ange nedan) Antal sidor 70 Presentationsdatum 2011-12-14

Publiceringsdatum (elektronisk version)

Institution och avdelning Institutionen för datavetenskap

URL för elektronisk version

http://www.ep.liu.se

Publikationens titel

Partialevaluering - Specialisering av program. Utformning av en laboration

Författare

Alexander Binggeli Sammanfattning

Partialevaluering av program är ett intressant ämne både för att optimera programmen men även för möjligheterna att kompilera program med en interpretator.

Denna rapport beskriver hur laborationerna i kursen Data och Programstrukturer på Linköpings Universitet, som är helt baserad på Abelson & Sussmans bok Structure and Interpretation of Computer Programs kan utökas så att den även går igenom partialevaluering. Som grund i laborationsunderlaget ligger en enkel partialevaluator för en delmängd av det funktionella språket Scheme som utvecklas för underlaget.

Rapporten består i huvudsak av tre delar, i början introduceras läsaren till partialevaluering. Vi förklarar hur man av ett generellt program och viss känd information vill skapa ett nytt specialiserat program. Två vanliga metoder att göra partialevaluering på beskrivs men i första hand fokuserar rapporten på den

metod som implementerats, nämligen offline metoden. Specialisering av ett program med en offline partialevaluator börjar med en analys av programmet som sedan specialiseras. Vi går igenom varje steg i analysen och specialiseringen innan implementationen introduceras.

I den andra delen av rapporten tittar vi närmre på den implementation som kommer användas I laborationsuppgifterna. Genom att beskriva språket som specialiseras och titta på kod för de funktioner som är intressanta förklaras hur partialevaluatorn fungerar.

Tillslut presenteras fem förslag på uppgifter kring några ämnen och problem som passar I laborationskursen. Uppgifterna försöker följa en liknande ordning som tidigare uppgifter i kursen. Vi börjar med en uppgift där man testar partialevaluatorn och undersöker resultatet av specialisering. Sedan följer tre uppgifter som tittar på hur funktionaliteten i partialevaluatorn kan utökas. Den sista uppgiften fokuserar på hur man kan kompilera ett mönster till en mönstermatchare med en

partialevaluator.

Nyckelord

(4)
(5)

Sammanfattning

Partialevaluering av program är ett intressant ämne både för att optimera programmen men även för möjligheterna att kompilera program med en interpretator. Denna rapport beskriver hur laborationerna i kursen Data och Programstrukturer[10] på Linköpings Universitet, som är helt baser-ad på Abelson Sussmans bok Structure and Interpretation of Comput-er Programs[8] kan utökas så att den även går igenom partialevaluComput-ering. Som grund i laborationsunderlaget ligger en enkel partialevaluator för en delmängd av det funktionella språket Scheme som utvecklas för underlaget. Rapporten består i huvudsak av tre delar, i början introduceras läsaren till partialevaluering. Vi förklarar hur man av ett generellt program och viss känd information vill skapa ett nytt specialiserat program. Två vanliga metoder att göra partialevaluering på beskrivs men i första hand fokuser-ar rapporten på den metod som implementerats, nämligen oine metoden. Specialisering av ett program med en oine partialevaluator börjar med en analys av programmet som sedan specialiseras. Vi går igenom varje steg i analysen och specialiseringen innan implementationen introduceras.

I den andra delen av rapporten tittar vi närmre på den implementation som kommer användas i laborationsuppgifterna. Genom att beskriva språket som specialiseras och titta på kod för de funktioner som är intressanta förk-laras hur partialevaluatorn fungerar.

Tillslut presenteras fem förslag på uppgifter kring några ämnen och prob-lem som passar i laborationskursen. Uppgifterna försöker följa en liknande ordning som tidigare uppgifter i kursen. Vi börjar med en uppgift där man testar partialevaluatorn och undersöker resultatet av specialisering. Sedan följer tre uppgifter som tittar på hur funktionaliteten i partialevaluatorn kan utökas. Den sista uppgiften fokuserar på hur man kan kompilera ett mönster till en mönstermatchare med en partialevaluator.

(6)

Innehåll

1 Inledning 1 1.1 Syfte . . . 1 1.2 Frågeställning . . . 1 1.3 Avgränsningar . . . 2 1.4 Metod . . . 2 2 Partialevaluering 3 2.1 Specialisering av interpretatorer . . . 5 2.2 Två typer av partialevaluering . . . 7 2.2.1 Online partialevaluering . . . 7 2.2.2 Oine partialevaluering . . . 8 2.3 Bindingstidsanalys . . . 9 2.3.1 Divisioner . . . 9 2.3.1.1 Monodivision . . . 9 2.3.1.2 Polydivision . . . 10 2.3.2 Annotering . . . 11 2.4 Specialisering . . . 11 3 Implementation 13 3.1 Språket - en delmängd av Scheme . . . 13

3.2 Bindningstidsanalysen . . . 14

3.2.1 Call unfolding on the y . . . 15

3.2.2 Kodduplicering . . . 15 3.3 Divisionsfasen . . . 16 3.3.1 Huvudfunktionen . . . 17 3.3.2 Skapa ny divisioner . . . 18 3.3.3 Let-uttryck . . . 18 3.4 Annoterings fasen . . . 19 3.4.1 Lift . . . 21 3.4.2 Annotering av funktionsapplikation . . . 21

3.4.3 Annotering av andra uttryck . . . 22

3.5 Specialiserings fasen . . . 22

(7)

3.5.2 Specialize . . . 23

3.5.3 Specialisering av statiska if-uttryck . . . 24

3.5.4 Specialisering av statisk funktionsapplikation . . . 24

3.5.5 Specialisering av dynamisk funktionsapplikation . . . . 25

3.5.6 Specialisering av statisk primitiv funktionsapplikation 25 3.5.7 Specialisering av let-uttryck . . . 26 4 Laboration 27 4.1 Laborationsskelett . . . 27 4.2 Laborationsinnehåll . . . 28 4.2.1 Tester . . . 28 4.2.2 Förbättring av bindningstidsanalys . . . 29 4.2.3 Kodduplicering . . . 31 4.2.4 Utökning . . . 32

4.2.5 Kompilering med partialevaluering . . . 32

5 Avslutande diskussion 35 5.1 Laborationsskelettet . . . 35 5.2 Uppgifterna . . . 36 5.3 Implementationen . . . 37 5.4 Vidare arbete . . . 37 A Källkod för partialevaluatorn 40

(8)

Figurer

2.1 Partialevaluering resp interpretering . . . 3

2.2 Icke specialiserad append funktion . . . 4

2.3 Kompilering med partialevaluator . . . 5

2.4 Mönstermatchare . . . 6 2.5 Specialiserad mönstermatchare . . . 7 2.6 Online partialevaluering . . . 8 2.7 Oine partialevaluering . . . 8 2.8 Division programexempel . . . 10 2.9 Monodivision . . . 10 2.10 Polydivision . . . 10 2.11 Annoterat program . . . 11

3.1 Partialevaluatorns olika steg . . . 13

3.2 Språkets syntax . . . 14

3.3 Kodduplicering exempel . . . 16

3.4 Kodduplicering resultat . . . 16

3.5 Divisioner som hittas i mönstermatcharen . . . 16

3.6 Annoterad syntax . . . 19

(9)

Kapitel 1

Inledning

1.1 Syfte

Syftet med detta examensarbete är att jag genom att experimentera och utveckla en enkel partialevaluator ska skaa mig tillräckligt med kunskaper om hur partialevaluering fungerar. För att sedan i denna rapport undersöka hur man kan gör ett antal laborationer inom ämnet partialevaluering. Den partialevaluator som jag utvecklat för att lära mig ämnet ska sedan även användas som grund i laborationsuppgifterna, dessa laborationer ska vara vara utformade så att de passar in i laborationsserien till kursen Data och programstrukturer[10] på Linköpings universitet.

Vi väljer att implementera en egen partialevaluator istället för att an-vända en existerande som tex SIMLIX[9], för att utvecklingen av en egen ger oss kunskapen om vad som passar till en laboration. Dessutom följer den egenutvecklade partialevaluator den stil som används i Abelson Sussmans bok Structure and Interpretation of Computer Programs[8] som ligger till grund för även de andra laborationerna i kursen Data och programstruktur-er.

En mindre och enklare partialevaluator som följer en liknande stil som tidi-gare uppgifter passar vårat syfte bättre än en mer kraftfull och komplicerad.

1.2 Frågeställning

• Skaa den kunskap som krävs för att kunna välja ut ämnen och problem för några laborationsuppgifter.

• Välja några ämnen och problem som är intressanta att undersöka i en laborationskurs, samt formulera ett antal exempeluppgifter på dessa.

(10)

1.3 Avgränsningar

I denna rapport kommer vi inte gå igenom eekterna av att självapplicera en partialevaluator och inga uppgifter på ämnet beskrivs. Partialevaluatorn som implementeras i kapitel 3 kommer därför inte heller vara självapplicer-bar. Vår partialevaluator kommer att specialisera en delmängd av det funk-tionella språket Scheme. Vi förutsätter att programmet som specialiseras är välskrivna och att inga sidoeekter förekommer. Destruktiva variabler tillåts inte heller.

1.4 Metod

Arbetet börjar med att ämnet studeras och att en enkel partialevaluator implementeras. Implementationen följer beskrivningen av hur en partiale-valuator för ett funktionellt språk fungerar i boken Partial Evaluation and Automatic Program Generation av Niel D. Jones, Carsten K. Gomard och Peter Sestoft[1].

När implementationen är färdig väljs några passande ämnen och problem ut och exempeluppgifter formuleras kring dem.

(11)

Kapitel 2

Partialevaluering

När man partialevaluerar ett program vill man av ett generellt program och kända värden för en del av dess parametrar skapa ett nytt specialiserat pro-gram, som givet resten av parametrarna kan köras vid ett senare tillfälle[1]. Förenklat görs detta genom att kända variabler ersätts med värdet, sedan beräknas de uttryck som går att beräkna med den kända informationen[6]. Resten av beräkningarna fördröjs genom att skapa kod som utför dem vid ett senare tillfälle.

A partial evaluator performs a mixture of execution and code generation actions  the reason Ershov called the process 'mixed computation'[1, s. 3]

Denna blandning av beräkning och kodgenerering innebär att en partial-evaluator måste ha funktionalitet från både en interpretator och en kompila-tor. Detta syns tydligt när man specialiserar ett program där alla värden är kända. Resultatet ska då bli detsamma som om programmet interpreterades. På samma sätt innebär specialisering av ett program med endast okända vär-den att programmet översätts till det språk specialiseraren genererar[2].

(12)

Figur 2.1 Illustrerar hur beräkningen vid partialevaluering delas upp i två steg, jämfört med beräkning i ett steg genom att interpretera programmet med all data.

Med det som i guren benämns statisk data menas de parametrar vars värden är kända vid specialiseringen. Med dynamisk data syftar vi på parame-trar vars värden är kända vid ett senare tillfälle. Om partialevaluatorn spe-cialiserar så mycket som det går bör det specialiserade programmet gå snab-bare att köra. Detta på grund av att alla statiska beräkningar och interpreterings-overheaden associerad med dem redan har utförts under specialiseringen[2].

Som exempel kan vi specialisera funktionen append som konkatenerar två listor med varandra.

(define (append x y) (if (null? x)

y

(cons (car x) (append (cdr x) y)))))) Figur 2.2: Icke specialiserad append funktion

Vi specialiserar append med den kända parametern x bunden till listan (a b c) medan parametern y är okänd. Parametern x blir då vår statiska data och y vår dynamiska data.

Då värdet på x är känt, betyder det att vi under specialiseringen kan beräkna (if (null? x)), (car x), (cdr x). Anropet till append kommer istället generera en ny specialiserad version av append för det nya värdet. (define (appendABC y) (cons 'a (appendBC y)))

(define (appendBC y) (cons 'b (appendC y))) (define (appendC y) (cons 'c (appendNIL y))) (define (appendNIL y) y)

Resultatet av specialiseringen är fem funktioner. Varje funktion är en append-funktion specialiserad på ett värde. Den första append-funktionen är specialiserad på listan (a b c), den andra på (b c) osv ner till den tomma listan.

För att förbättra resultatet ytterligare kan funktionsanropen ersättas med funktionskroppen. Då får vi följande resultat:

(define (appendABC y)

(cons 'a (cons 'b (cons 'c y))))

Resultatet av specialiseringen är den nya funktionen appendABC som är en specialiserad version av append som alltid konkatenerar listan (a b c) med listan y. Vi ser även att den nya funktionen kräver färre funktionsanrop för att konkatenera de två listorna än vad originalfunktionen kräver för samma listor.

Denna generering av specialiserad program gör att partialevaluering ib-land även kallas för programspecialisering.

(13)

2.1 Specialisering av interpretatorer

En interpretator är en procedur som utför instruktionererna i ett uttryck genom att beräkna dem[8]. För att göra det behövs uttrycket men även vilka körtidsvärden variablerna har, detta kallas för omgivningen. I en in-terpretator kan uttrycket vara känt medan omgivningen först blir känd vid evalueringen.

Vi kan då specialisera en interpretator med ett statiskt1 uttryck och en

dynamisk2 omgivning. Resultatet blir en specialiserad version av

interpreta-torn som endast interpreterar uttrycket den blev specialiserad med[6]. Om interpretatorn evalueras med en omgivning blir resultatet desamma som om originaluttrycket hade interpreterats med omgivningen.

Figur 2.3: Kompilering med partialevaluator

Figur 2.3 illustrerar hur en interpretator för språket M, skriven i språket S, specialiseras på ett program skrivet i M. Även partialevaluatorn är skriven i språket S. Det resulterande programmet är skrivet i språket S. Programmet är därmed översatts från språket M till språket S.

Denna möjlighet att med en partialevaluator och en interpretator kompilera ett program beskrevs för första gången av Futamura, Y[5]. Den brukar därför kallas för Futamuras första projektion[1].

Vi kan titta på två exempel för att klargöra lite mer varför resultatet blir som det blir.

(cond .

((variable? exp) (lookup-variable-value exp env)) .)

Denna rad kommer från den metacirkulära evaluator som används i kursen Data och Programstrukturer[10]. Det koden gör är att om det aktuella ut-trycket exp är en variabel, så ska variabelns värde hämtas från omgivningen env.

Vi specialiserar denna evaluator med exp som statisk och env som dy-namisk. När en variabel hittas vill vi hämta värdet, villket inte går då env

1känt 2okänd

(14)

är okänd. Istället genereras den kod som vid ett senare tillfälle kan hämta värdet.

Om exp har värdet x kan vi tänka oss att följande kod genereras: (lookup-variable-value 'x env)

I resulatet ovan kan vi se att interpreterings-overheaden på detta uttryck har kompilerats bort eftersom alla beräkningar som beror på exp har beräknats vid specialiseringen.

(define patm '((define (patmatch pat l) (if (null? pat)

(null? l)

(if (equal? (car pat) '!!) (segmatch (cdr pat) l) (if (null? l)

#f

(if (equal? (car pat) (car l)) (patmatch (cdr pat) (cdr l)) #f)))))

(define (segmatch pat l) (if (null? pat)

#t

(if (patmatch pat l) #t

(if (null? l) #f

(segmatch pat (cdr l)))))))) Figur 2.4: Mönstermatchare

Ytterligare ett exempel är den mönstermatchare som vi ser i gur 2.4. Pro-grammet har två parametrar, ett mönster och en lista. När proPro-grammet eval-ueras returnerar det sant om listan matchar mönstret. Ett mönster består av segment och symboler, segmenten matchar mot alla följder av symboler. Vi kan se mönstret som ett inbyggt språk och vår mönstermatchare som en interpretator för detta språket. Om den specialiseras med mönstret (!! a !!) som accepterar alla listor som innehåller minst ett a får vi som resultat ett program som endast accepterar samma listor.

(15)

((define (patmatchsd!!a!!l l) (segmatchsda!!l l)) (define (segmatchsda!!l l)

(if (patmatchsda!!l l) '#t

(if (null? l) '#f (segmatchsda!!l (cdr l))))) (define (patmatchsda!!l l)

(if (null? l) '#f

(if (equal? 'a (car l)) (patmatchsd!!l (cdr l)) '#f))) (define (patmatchsd!!l l) (segmatchsdl l))

(define (segmatchsdl l) '#t))

Figur 2.5: Specialiserad mönstermatchare

Ovan ser vi det resulterande programmet. Specialiseringen genererade fem specialiserade funktioner, vi ser att mönstret har beräknats bort ut pro-grammet. Symbolen är dock kvar i en villkorssats.

Det intressantaste är de två segmatch funktionerna som har skapats av båda segmenten. Den första segment funktionen går igenom listan tills ett a hittas eller den är tom, det är faktiskt vad det första segmentet ska göra. Den andra funktionen har genererats av segmentet efter att ett a har hittats. Den returnerar alltid sant eftersom ett a redan har hittats så behöver inte resten av listan matchas. I kapitel 3 och 4 går vi igenom den specialiserade mönstermatcharen mer i detalj.

2.2 Två typer av partialevaluering

Partialevaluatorer kan delas in i två grupper online eller oine beroende på om analysen och specialiseringen sker samtidigt eller vid olika tillfällen. Vi kommer att fokusera på oinemetoden då den partialevaluator som beskrivs i senare kapitel är oine. Men vi kommer att kortfattat förklara online meto-den och dess för och nackdelar.

2.2.1 Online partialevaluering

De första partialevaluatorerna som gjordes följde online metoden[2], dvs besluten om vilka uttryck som ska beräknas och för vilka kod ska gener-eras görs samtidigt som specialiseringen sker.

(16)

Figur 2.6: Online partialevaluering

En online partialevaluator fungerar som en lite annorlunda evaluator på det sättet att den tillåter okända värden i omgivningen och endast utför de beräkningar som går[2]. Under specialiseringen använder sig specialiser-aren av variablernas värden för att avgöra vilka uttryck som är statiska eller dynamiska. Därefter beräknas uttryck som går att beräkna medan kod gener-eras för resten. En specialiserare som avgör vad som ska specialisgener-eras, sam-tidigt som den beräknar eller genererar kod har fördelen att den vet vilket värde variablerna har och det innebär att den i vissa fall kan göra bättre beslut[2, 3]. Problemet är att om valen och specialiseringen sker samtidigt blir specialiserings funktionen i online partialevaluatorer större än oine varianten[3].

2.2.2 Oine partialevaluering

Oine metoden delar upp processen i två steg, först analyseras koden för att avgöra vilka uttryck som kommer kunna beräknas samt för vilka uttryck kod måste genereras. Sedan specialiseras det analyserade programmet. Re-sultatet av analysen är ett det analyserade programmet fast i s.k annoterad form. Den annoterade koden innehåller den information som krävs för att specialiseraren ska veta vad som är statiskt respektive dynamiskt.

Figur 2.7: Oine partialevaluering

Den annoterade koden kan innehålla instruktioner om vad som ska beräk-nas eller vad som ska bli kod. Det innebär att specialiserings fasen blir relativt liten och enkel att skriva då den bara kommer att följa de instruktioner som står i koden.

Eftersom analysen sker innan specialiseringen vet programmet inte om vilka värden som de statiska variablerna har utan endast vilka variabler som är statiska, vilket kan resultera i sämre analys än en ren online metod. Resultatet kan förbättras genom att göra viss online analys även under specialiseringen[2, 3].

(17)

2.3 Bindingstidsanalys

Innan någon specialisering av programmet kan ske, måste koden analyseras. Denna analys kallas för bindningstidsanalys vars syfte är att ta reda på bind-ningstiden av alla variabler, så att vi vet om värdet kommer vara känt vid specialisering eller ej.

En variabels bindningstid är statisk om dess värde är känt och dynamiskt om det är okänt. En bindningstidsomgivning är en mängd av bindningstider för era variabler. Vi har under själva analysen ingen kunskap om vilka värden variabler har, vi vet endast vilka som är statiska resp dynamiska. Bindningstidsanalysen sker i två steg:

• I första steget hittar vi alla 'så kallade' divisioner av funktionsanrop i programmet.

• I andra steget använder vi divisionerna för att bädda in informationen i koden genom att annotera koden i en annan syntax.

2.3.1 Divisioner

För att kunna skapa specialiserade versioner av funktioner måste vi veta vilka bindningstider våra parametrar har vid de olika funktionsanropen. Därför börjar analysen med att alla divisioner hittas. En division är en mappning mellan ett funktionsnamn och en eller era bindningstidsomgivningar.

Resultatet av analysen är att alla variabler ska ha en bindningstid. Al-goritmen för att hitta alla divisionerna sägs vara säker om de divisioner den genererar är kongruenta. En division är kongruent om de parameter som klassicerats som statiska endast kommer bindas till värden, aldrig till uttryck. Däremot kan en dynamisk parameter säkert bindas till både vär-den och uttryck[1]. Det är dock önskvärt att märka alla parametrar som är statiska rätt, eftersom märkning av en statisk parameter som dynamisk kommer leder till mindre specialisering än vad som är möjligt.

2.3.1.1 Monodivision

En monodivision är en mappning mellan ett funktionsnamn och en bind-ningstidsomgivning. Om en funktion anropas vid er tillfällen med olika bindningstidsomgivningar måste en kongruent division skapas som är säker för båda anropen.

Kongruenskravet uppfylls genom att algoritmen endast får byta ut en statisk bindningstid mot en dynamisk. Om vi hittar en ny omgivning där en av parametrarna är dynamisk men samma parameter har klassicerats som statisk i den division vi har för funktionen. Då uppdateras den parameterns bindningstid till dynamisk.

(18)

Som exempel kan vi titta på vilka divisioner som hittas när vi analyserar följande program med x som statisk och y som dynamisk. Append funktio-nen är Schemes inbyggda funktion och inte egendenierad därför hittas inga divisioner för den.

(define (f x y)

(append (g x) (g y))) (define (g x)

(list x x))

Figur 2.8: Division programexempel Resultatet blir två divisioner, en för varje funktion. (f (s d)) (g (d))

Figur 2.9: Monodivision

Funktionen g anropas vid två tillfällen, en gång med en statisk parameter och en gång med en dynamisk. Men detta fall säger analysen att g är en helt dynamisk funktion och därmed inte kan specialiseras.

Nackdelen med att använda sig av denna metod är att då en funktion anropas er gånger med olika bindningstider kommer vi endast att specialis-era denna funktion på den minst dynamiska divisionen som fungspecialis-erar för alla anrop. Vilket kommer att resultera i mindre specialisering än vad som är möjligt.

2.3.1.2 Polydivision

Ett annat sätt är att skapa så kallade polydivisioner, detta för att specialisera mer av programmet.

Polydivisioner skiljer sig från monovarianten genom att den istället för en bindningstidsomgivning för varje funktion har en mängd av omgivningar, en för varje bindningstidsomgivning som funktionen anropas med[1, 4]. Efter-som en ny omgivning skapas för varje anrop löser det kongruenskravet då en statisk variabel aldrig binds till ett dynamiskt värde.

Vi analyserar funktionen i gur 2.8 igen fast skapar polydivisioner istället och får följande resultat:

(f (s d)) (g (s) (d))

Figur 2.10: Polydivision

För funktionen g får vi nu två bindningstidsomgivningar vilket innebär att det första anropet till g kan beräknas medan det andra blir kvar. I detta

(19)

exempel gör det inte så stor skillnad men man kan tänka sig att i ett större program kan vinsten bli mer märkbar.

2.3.2 Annotering

De divisioner som hittas kommer att ge information om vilka bindningstid-somgivningar alla funktionsanrop har. Ur en division kan man sen härleda vilka av de andra uttrycken i funktionskroppen som går att beräkna. Om man inte annoterar koden måste det göras vid specialiseringen.

Vid annoteringen skapar man en unik annoterad funktion av varje bind-ningstidsomgivning för funktionsanrop så det spelar ingen roll vilken typ av divisioner man har hittat då metoden fungerar för båda typerna. Varje an-rop till en sammansatt funktion ersätts även med ett anan-rop till den specika annoterade versionen för just den omgivningen. Dessutom vill vi märka upp vilka anrop som är statiska respektive dynamiska, vilket vi gör genom att in-föra en statisk och en dynamisk version av alla konstruktioner i språket. Om vi använder oss av samma annotering som vi gör i den partialevaluator som beskrivs i nästa kapitel, kommer annoteringen av de divisioner som hittades i gur 2.10 se ut på följande vis:

((define ((f (s d)) (x y)) (opd (list

(lift (calls ((g (s) (2)) (x)))) (calld ((g (d) (2)) (y)))))) (define ((g (d)) (x)) (opd (list x x))) (define ((g (s)) (x)) (ops (list x x))))

Figur 2.11: Annoterat program

Annoteringen har skapat två versioner av funktionen g, en dynamisk och en statisk. Den dynamiska funktionen innehåller endast en dynamisk primitiv-applikation och den statiska en statisk primitiv-applikation. Då det nu nns två versioner innebär det att de två applikationerna av g kan spe-cialiseras var för sig. Den dynamiska funktionen kommer inte spespe-cialiseras medan den statiska kommer beräknas.

2.4 Specialisering

När analysen av programmet är färdig behöver vi de statiska variablernas värden för att kunna specialisera programmet. Den funktion som kommer att utföra specialiseringen kan ses som en interpretator för vår annoterade kod.

Programmet specialiseras vid ett antal tillfällen. Funktionsapplikationer som är säkra öppnas upp och anropet ersätts med funktionskroppen. Det

(20)

är säkert att öppna upp ett funktionsanrop om det inte resulterar i att vi fastnar i en oändlig loop eller om det inte resulterar i duplicerad kod eller beräkning.

Primitiva funktionsapplikationer som är statiska beräknas och resulterar i ett värde. Villkorsaster med ett statiskt villkor beräknas och resulterar i att endast den grenen som stämmer returneras. Vid lokala variabeldeklarationer utökas omgivningen om uttrycket som variabeln tilldelas är statiskt.

Vi kan titta på resultatet genom att specialisera vår annoterade kod i gur 2.11 med variabeln x bunden till värdet 'a resultatet blir det följande specialiserade programmet:

((define (fsday y) (append '(a a) (gdx y))) (define (gdx x) (list x x)))

I resultatet syns hur den statiska funktionsapplikationen till

(calls ((g (s) (2)) (x))) har ersatts med funktionskroppen. Innan ap-plikationen öppnats upp har funktionskroppen specialiserats och eftersom den endast innehåller en statisk primitiv-applikation resulterar det i värdet (a a).

(21)

Kapitel 3

Implementation

I detta kapitel kommer den partialevaluator som används i laborationskursen att beskrivas. Syftet med detta är, att genom experiment skaa mig de kun-skaper om ämnet som krävs för att kunna göra de laborationsuppgifter som beskrivs i kommande kapitel.

Partialevaluatorn ska användas för att ge studenterna ett program att ex-perimentera med för att få en inblick i hur partialevaluering fungerar. Bind-ningstidsanalysen är beskriven i tidigare kapitel och försöker följa beskrivnin-gen av partialevaluering för ett funktionellt språk i boken Partial evaluation and Automatic Program generation[1].

Figur 3.1: Partialevaluatorns olika steg

Den partialevaluator som implementerats använder sig av en statisk bind-ningstidsanalys innan programmet specialiseras vilket innebär att den följer oinemodellen. Analyssteget är uppdelat i två mindre steg där programmet först hittar de divisioner som nns och sedan i ett andra steg använder dessa för att annotera koden.

3.1 Språket - en delmängd av Scheme

Den partialevaluator som beskrivs i detta kapitlet specialiserar en delmängd av den funktionella delen av programmeringsspråket Scheme. För vårt syfte passar Scheme bra av era anledningar, både som implementationsspråk och som det språk som specialiseras.

(22)

• Kursen Data och Programstrukturer[10] som laborationerna ska utöka använder sig av Scheme, då bör även dessa labbar göra det.

• Språket är lätt att parsa då det redan är i AST1-form.

• Eftersom en partialevaluator är ett program som opererar på andra program är det lättare att skriva en i ett språk som Scheme där program representeras som en lista[6]

• Mycket forskning har gjorts och era partialevaluatorer har skrivits för Lisp liknande språk. Därför nns det mycket information om hur sådana implementeras.

<Program> = (<Definition> ... <Definition>)

<Definition>= (define (<FuncName> <ParamList>) <Expression>) <ParamList>= <Var> ... <Var>

<Expression>= <Constant> | <Var>

| (if <Expression> <Expression><Expression>) | (<FuncName> <Arglist>)

| (<Operator> <Arglist>)

| (let <Binding> <Expression>) <Arglis>= <Expression> ... <Expression>

<Binding>= ((<Var> <Expression>) ... (<Var> <Expression>)) <Constant>= <Numeral>

| (quote <Value>)

<Operator>= car | cdr | cons | list | + | - | = ... Figur 3.2: Språkets syntax

Figuren 3.2 beskriver språkets syntax, vi ser att det språk som evalueras är en begränsad version av Scheme. Vi förutsätter dessutom att det inte nns några sidoeekter i programmet tex utskriftsfunktioner eller inkrementering av globala variabler. Sidoeekter innebär nämligen problem då det kan hända att de beräknas bort eller dupliceras under specialiseringen.

3.2 Bindningstidsanalysen

Eftersom vår partialevaluator är oine utförs en så kallad statisk bind-ningsanalys innan programmet specialiseras. Resultatet av analysen är käll-programmet i annoterad kod där alla uttryck är märkta som statiska eller dynamiska.

(23)

För att språket ska gå att analysera måste vi deniera när olika uttryck är statiska resp dynamiska, samt om resultatet av att specialisera ett uttryck är statiskt eller dynamiskt.

• Ett if uttryck är statiskt endast om villkorsuttrycket är statiskt. Re-sultatet av ett dynamiskt if uttryck är alltid dynamiskt, reRe-sultatet av ett statiskt if uttryck är statiskt om båda grenarna är statiska annars är det dynamiskt.

• Applikation av en operator är statisk endast om alla parametrar är statiska. Resultatet av en statisk operator är statiskt medan resultatet av en dynamisk operator är dynamiskt.

• Applikation av en sammansatt funktion är statisk om alla parametrar är statiska samt om anropet står i en gren av en statisk if sats och ingen av de dynamiska parametrarna förekommer er än en gång i kroppen. Resultatet är statiskt endast om alla parametrar är statiska annars är det dynamiskt.

3.2.1 Call unfolding on the y

För att förbättra resultatet utförs även så kallad call-unfolding-on-the-y vilket innebär att dynamiska funktionsanrop kommer att öppnas upp och ersättas med deras funktionskropp om det är säkert. När anropet ersätts med kroppen substitueras de formella parametrarna med de aktuella värdena. Det är säkert att öppna upp ett anrop om det inte resulterar i att vi fastnar i en oändlig loop och om det inte resulterar i att kod eller beräkning dupliceras. Exempelvis kan vi titta på den tidigare nämnda append funktionen i gur 2.2. Om vi specialiserar append med den första listan som dynamisk och den andra statisk. Det innebär att if-uttryckets villkor är dynamiskt och om vi försöker öppna upp anropet till append kommer programmet att fastna i en oändlig loop, eftersom det inte nns något villkor som säger till när vi ska sluta.

Vi löser detta genom att endast öppna upp funktionsanrop om anropet står i en gren av en statisk if. Då vet vi att såvida det inte redan fanns en oändlig loop i ursprungsprogrammet kommer vi sluta öppna upp funktion-sanrop någon gång då det beror på ett känt värde.

3.2.2 Kodduplicering

Vi vet nu att vi aldrig kommer fastna i en oändlig loop när ett dynamiskt funktionsanrop öppnas upp. Men det kan hända att det specialiserade pro-grammet är mindre eektivt än ursprungspropro-grammet.

(24)

Som ett exempel kan vi titta på följande program: (define (f n) (if (= n 0) 1 (g (f (- n 1))))) (define (g m) (+ m m))

Figur 3.3: Kodduplicering exempel

Om anropet till funktionen g ersätts med g:s funktionskropp kommer vi få följande resultat.

(define (f n) (if (= n 0) 1 (+ (f (- n 1)) (f (- n 1))))) Figur 3.4: Kodduplicering resultat

Ursprungsprogrammet har linjär tidskomplexitet medan det utvecklade programmet har exponentiell tidskomplexitet. Vi måste ha en metod för att förhindra detta, därför ändrar vi vårt program på följande vis. Ett anrop till en funktion får utvecklas endast om anropet står i en gren av en statisk villkorssats och att inga av dess dynamiska variabler förekommer mer än en gång i funktionskroppen.

3.3 Divisionsfasen

Nedan förklaras den första fasen i bindningstidsanalysen. Detta görs genom att vi först tittar på funktionen som traverserar programmet och sedan på vad som sker när en funktionsapplikation hittas. I denna första fas är ap-plikation av en sammansatt funktion mest intressant för att det är då vi skapar nya divisioner.

De divisioner som analysen genererar är av typen polydivision och sparas i variabeln divisions. En division representeras här som en lista innehål-lande funktionsnamnet och de bindningstidsomgivningar som hittats. ((f (s d)(s s)) (g (d s d) (s s d)))

Analysen påbörjas genom att anropa funktionen finddivisions. För att hitta divisionerna krävs det att vi skickar med programmet som ska analyseras samt vilka parametrar som är statiska resp dynamiska. Detta görs genom att skicka med en första division.

Låt oss säga att vi vill hitta divisionerna när vi analyserar mönster-matcharen med (patmatch (s d)) som första division.

((patmatch (s d)) (segmatch (s d)))

(25)

Resultatet är två divisioner en för varje funktion i mönstermatcharen. Detta program tjänar inget på att använda sig av polydivisioner för ingen funktion anropas någonsin med listan som statisk och mönstret som dy-namisk.

3.3.1 Huvudfunktionen

Funktionen find-div är kärnan av analysen då det är denna funktion som traverserar programmet och anropar olika funktioner beroende på vad exp är för något uttryck.

define (find-div exp param sdmap divisions) (cond

((self-evaluating? exp) exp) ((quoted? exp) exp)

((if? exp) (div-if exp param sdmap divisions))

((begin? exp) (div-expr-list (begin-expr-list exp) param sdmap divisions)) ((let? exp) (div-let exp param sdmap divisions))

((variable? exp) exp) ((application? exp)

(if (primitive-application? exp)

(div-primapp exp param sdmap divisions) (div-normapp exp param sdmap divisions))) (else (error 'find-div "Unkown expression"))))

En sådan konstruktion som ovan förkommer i alla tre faser av vår partiale-valuator. Det är en vanlig förekommande struktur när man vill traversera ett program och nns även i den metacirkulära evaluator i boken Structure and Interpretation of Computer Programs [8]. Denna funktion skiljer sig dock från den av en evaluator då vi här inte kan beräkna ett värde och därmed inte behöver slå upp variabelvärden i omgivningen. Dessutom händer det inte så mycket vid anrop till let, if och primitiv-applikation då dessa bara fortsätter att traversera för att hitta nya divisioner.

(26)

3.3.2 Skapa ny divisioner

I detta steg av analysen är applikationerna av sammansatta funktioner mest intressant. Det är här som nya divisioner skapas.

(define (div-normapp exp param sdmap divisions) (let* ((div-param

(div-param-list (app-params exp)

param sdmap divisions)) (fun-sdmap (make-sdmap div-param param sdmap)) (fun-div (make-division (app-name exp) fun-sdmap)) (newdiv (add-new-division fun-div divisions))) (if newdiv

(finddivisions program fun-div divisions)) (if (static-sdmap? fun-sdmap)

(list 'calls (app-name exp)) (list 'calld (app-name exp)))))

När en funktionsapplikation hittas anropas denna funktion. Här analy-seras applikationens parametrar genom att anropa div-param-list. För att skapa divisionens nya bindningstidsomgivning undersöks vilka av de analy-serade parametrarna som är statiska resp dynamiska, detta gör anropet till make-sdmap.

Vi skapar en ny division av funktionens namn och bindningstidsom-givningen som make-sdmap hittade. Funktionen add-new-division lägger till divisionen i divisions om den inte redan existerar och returnerar sant eller falskt beroende på om den lades till. Om det är en ny division an-ropas finddivisions med den nya divisionen och analysen börjar om på den nya funktionen. Analysen kommer att terminera för att vi endast anal-yserar kroppen på funktionen om en ny division hittas.

Den sista inkapslingen av funktionsnamnet i calls eller calld görs en-dast för att inte behöva analysera parametrarna igen när man ska avgöra om anropet är statiskt eller ej.

För att visa vad resultatet blir kan vi titta på ett exempelanrop: (div-normapp '(g (list 'a 'b) x y 5) (x y) (s d) divisions)

Divisionen som skapas av detta anropet är: (g (s s d s))

3.3.3 Let-uttryck

Förutom applikation av sammansatta funktioner är även lokala denitioner i form av let-uttryck intressanta. Detta för att vi måste beräkna lokala vari-ablers bindningstid och utöka omgivningen med de nya bindningstiderna. När det är gjort analyseras resten av kroppen. Utökningen av omgivningen går vi igenom i större detalj i kapitel 3.5.7 när let-uttryck specialiseras.

(27)

3.4 Annoterings fasen

När alla divisioner är hittade kan man använda dessa för att annotera koden. Annoteringen sker i len annotete.ss. Det är den annoterade koden som avgör hur specialiseringen kommer gå till.

Annoteringen fungerar så att för varje bindningstidsomgivning i divi-sionerna skapas en ny funktion. Funktionskroppen traverseras sedan av en funktion som liknar find-div i det förra kapitlet. Denna funktion anropar underfunktioner som analyserar uttryck och skapar den annoterade koden. Vi kommer titta närmre på två annoterings-funktioner nämligen

annotate-normal-app och annotate-let. <Expression>= <Constant>

| <Var>

| (ifs <Expression> <Expression> <Expression>) | (ifd <Expression> <Expression> <Expression>) | (calls (<FuncName> (<Arglist>))

| (calld (<FuncName> (<Arglist>)) | (ops (<Operator> <Arglist>)) | (opd (<Operator> <Arglist>)) | (let <Binding> <Expression>) | (lift <Expression>)

Figur 3.6: Annoterad syntax

I gur 3.6 beskrivs den annoterade syntaxen. Vi visar endast syntaxen för uttrycken då det endast är dem som förändras.

I den nya syntaxen kapslas primitiva funktionsapplikationer in i ops eller opd beroende på den är statisk eller dynamisk. Samma gäller för applikation för sammansatta funktioner som kapslas in i calls eller calld. Vi har nu även två versioner av if-uttryck, ett statiskt och ett dynamiskt. Det är med dessa nya uttryck vi sparar resultatet av analysen.

Annoteringen börjar med ett anrop till annotate med programmet som ska annoteras och de divisionerna find-divisions hittade. Resultatet i form av annoterad kod av program returneras sedan av funktionen.

(28)

Vi fortsätter med analysen av programmet med de divisioner som hit-tades i gur 3.5.

((define ((patmatch (s d)) (pat l)) (ifs (ops (null? pat))

(opd (null? l))

(ifs (ops (equal? (ops (car pat)) '!!)) (calls ((segmatch (s d) (3 3))

((ops (cdr pat)) l))) (ifd (opd (null? l))

(lift #f)

(ifd (opd (equal? (lift (ops (car pat))) (opd (car l))))

(calld

((patmatch (s d) (4 3))

((ops (cdr pat)) (opd (cdr l))))) (lift #f))))))

(define ((segmatch (s d)) (pat l)) (ifs (ops (null? pat))

(lift #t)

(ifd (calls ((patmatch (s d) (4 3)) (pat l))) (lift #t)

(ifd (opd (null? l)) (lift #f)

(calld

((segmatch (s d) (3 3))

(pat (opd (cdr l)))))))))) Figur 3.7: Annoterad mönstermatchare

Annoteringen skapade en funktion för varje division och annoterade sedan funktionskroppen. Varje anrop har kapslats in i och vi kan se om anropet har klassicerats som statiskt eller dynamiskt. Alla if-uttryck har även bytts ut mot en statisk eller dynamisk version beroende på om villkoret är känt.

Funktionsdenitionernas namn har bytts ut mot den division som ska-pade funktionen, detta görs för att skilja på dem om era versioner hade skapats.

I calls eller calld uttrycken ser vi att efter bindningstidsomgivningen står en lista med tal i. De visar hur många gånger motsvarande variabel förekommer.

Alla uttryck som beror på mönstret är statiska och kommer därmed kun-na beräkkun-nas under specialiseringen. I kapitel 4 går vi i mer detalj igenom den annoterade mönstermatcharen.

(29)

3.4.1 Lift

I detta kapitel har vi infört den nya konstruktionen lift. Annoteringen kapslar in statiska uttryck i lift om uttrycket står i en dynamisk omgivning.

För att illustrera varför man vill göra detta kan vi titta på följande kodex-empel:

(opd (cons x (ops (list 'a 'b))))

Detta är den annoterade versionen av uttrycket (cons x (list 'a 'b)) där x är en dynamisk variabel.

Om vi specialiserar uttrycket kommer vi få den följande koden: (cons x (a b))

Vi får detta resultat eftersom specialiseringen av anropet till funktionen list kommer returnera värdet av (list 'a 'b) vilket är (a b). När resultatet interpreteras med ett värde bundet till x kommer vi få ett fel. Interpretatorn kommer att tolka (a b) som en applikation av funktionen a. Lift löser detta genom att ta resultatet av specialiseringen och kapsla in det i quote.

3.4.2 Annotering av funktionsapplikation

Funktionen annotate-normal-app är ett bra exempel på hur de esta ut-tryck annoteras. Det är egentligen bara let-utut-tryck som skiljer sig något i annoteringen då man måste hantera bindningstiden av de lokala variabler-na. Det är i denna funktion det avgörs om funktionsapplikationen är statisk eller dynamisk.

(define (annotate-normal-app exp param sdmap unf p) (let* ((ann-params (annotate-exp-list (app-params exp)

param sdmap unf p)) .

.

(if (or (static-param-list? ann-params param sdmap) (and unf safe-dupl))

(static-normapp (app-name exp) new-sdmap ann-params occ) (dynamic-normapp (app-name exp)

new-sdmap ann-params occ))))

För att underlätta läsningen har funktioner som inte är nödvändiga för förståelsen tagits bort, men om man vill läsa hela koden nns den i bilaga A. Alla annoterings-funktioner börjar med att annotera de aktuella parame-trarna, det görs genom att anropa ann-params som anropar huvudfunktioner med varje parameter. Den annoterade parameterlistan används sen för att

(30)

avgöra om alla parametrar är statiska, detta görs i static-param-list?. Variabeln unf är ett sanningsvärde som är sant om applikationen står i en gren av en statisk if-sats, det är detta som resulterar i att vi får call unfolding on the y. Safe-dupl är ett sanningsvärde som är sant om det är säkert att öppna upp funktionsapplikationen. Beroende om uttrycket är statiskt eller ej anropas static/dynamic-normap som skapar rätt kod.

Som ett exempel på hur ett program annoteras kan vi titta på vad som händer när funktionen g i gur 2.8 annoteras.

(annotate-normal-app (g x) (x y) (s d) #t p) Den annoterade versionen av koden ovanför blir: (calls ((g (s) (2)) (x))))

Resultatet blir en statisk funktionsapplikation på funktionen g. Siran i parantesen visar hur många gånger varialel x används i g:s funktionskropp.

3.4.3 Annotering av andra uttryck

Annotering av if-uttryck börjar med att villkoret annoteras som används för att avgöra om uttrycket är statiskt eller ej. Efter det annoteras grenarna och kod skapas för antingen en statisk eller dynamisk if. Primitiv-applikation annoteras på samma sätt som sammansatt fast med andra krav för när den är statisk eller ej. Det enda som skiljer annotering av let-uttryck är att bind-ningstidsomgivningen måste utökas beroende på om de tilldelade variablerna är statiska eller ej.

Koden för utökningen fungerar på ett liknande vis som vid specialisering av let därför refererar vi till det kapitlet och bilagorna för mer information.

3.5 Specialiserings fasen

Nu när bindningstidsanalysen är klar och koden är annoterad kan vi börja specialisera programmet. Specialiseringsfunktionen är en interpretator för den annoterade koden och utför de instruktioner som nns. Likheten med interpretatorn i tidigare laborationer är nu ännu tydligare.

För att kunna specialisera programmet behövs variablernas värden. För detta har vi nu en lista med aktuella parametrar i variabeln actpar, som ersätter bindningstidsomgivningen i sdmapp från tidigare stegen. Actpar till-sammans med variabeln formpar blir vår omgivning som kommer vara en mappning mellan ett variabelnamn och ett värde. Denna omgivning skiljer sig dock från den av en interpretator då det är tillåtet att ett värde är okänt. Om en variabel är okänd refererar den oftast endast till sig själv.

Vi har infört två nya variabler som heter d r, som står för done och remaining. Done innehåller namn och värden på de funktioner som redan

(31)

har specialiserats. Medan remaining innehåller namnen på de funktionerna som ska specialiseras samt värden på parametrar som den ska specialiseras med. Specialiseringen är klar när remaining är tom.

3.5.1 Specialisering av mönstermatchare

Specialiseringen går till så att det första icke specialiserade funktionsanropet plockas från remaining och specialiseras och läggs sedan i done. Varje gång en funktion anropas kollar vi i done om ett anrop till den funktionen med samma värden redan har specialiserats. Om den inte nns i done lägger vi den i remaining. Detta görs för att vi inte ska specialisera ett funktionsanrop era gånger om en funktion anropas med samma värde vid olika tillfällen.

Specialiseringen påbörjas genom att anropa spec med det annoterade programmet och de värden som den ska specialiseras med.

Om vi ska specialisera den annoterade versionen av mönstermatcharen kom-mer värdena se ut på följande vis (patmatch (s d) ((!! a !!) l)). Först står namnet på funktionen som ska specialiseras sedan bindningstidsom-givningen för dess parametrar och sist en lista med värdena den ska spe-cialiseras med, lägg märke hur den dynamiska variabeln l har sig själv som värde. Resultatet blir koden i gur 2.5

Vid specialiseringen har programmet skapat en specialiserad version av funktionerna för varje unikt statiskt värde dom anropats med. Vilket värde funktionerna har specialiserats med kan man se på namnen. Det nya namnet består av funktionens namn sammansatt med dess bindningstidsomgivning och de värden den specialiserats med. Som ett exempel kan vi titta på det första anropet och de värden som vi skickade med

(patmatch (s d) ((!! a !!) l)) , det genererade den första funktionen patmatchsd!!a!!l i det resulterande programmet. I kapitel 4 går vi igenom den specialiserade mönstermatcharen mer på djupet.

3.5.2 Specialize

Då denna funktion skiljer sig lite från hur de andra två såg ut tittar vi lite på en nedskuren variant av specialize.

(32)

(define (specialize exp formpar actpar p d r) .

((variable? exp) (lookup-variable exp formpar actpar)) ((quoted? exp) (text-of-quotation exp))

((ifs? exp) (spec-ifs exp formpar actpar p d r)) ((ifd? exp) (spec-ifd exp formpar actpar p d r))

. ((lift? exp)

(list 'quote (specialize (lift-exp exp)

formpar actpar p d r))) (else (error 'specialize "Unkown expression type"))))) Vår specialiserings fas blir kan liknas vid en lite annorlunda interpreta-tor för det annoterade programmet. Det innebär att funktionen special-ize skiljer sig från de motsvarande konstruktionerna i de tidigare faser-na. Här slår upp värden av variabler i omgivningen samt hämtar värdet av quotade uttryck. Vi kommer titta närmre på ett par funktioner nämli-gen spec-ifs, spec-calls, spec-calld, spec-ops och spec-let efter-som det är i stor del i dessa funktioner där vi specialiserar programmet.

I funktioner som spec-opd och spec-ifd är tillvägagångssättet mer likt annotate för att vi specialiserar parametrar och genererar kod för uttrycket. Man ser även hur lift quotar uttrycken efter att de har specialiserats.

3.5.3 Specialisering av statiska if-uttryck

Specialisering av ett statiskt if-uttryck kommer att beräkna villkoret och specialisera motsvarande gren.

(define (spec-ifs exp formpar actpar p d r)

(if (specialize (if-cond exp) formpar actpar p d r) (specialize (if-conseq exp) formpar actpar p d r) (specialize (if-else exp) formpar actpar p d r)))

Trots att specialiseringen av ett statiskt if-uttryck är relativt enkel gör den stor skillnad på resultatet eftersom det är här vi slänger bort kod som inte behövs. Här specialiserar vi först villkorsfunktionen och eftersom bindningstidsanalysen har klassicerat funktionen som statisk vet vi att vi kommer få ett sanningsvärde som resultat, förutsatt att programmet är ko-rrekt skrivet. Sedan specialiserar vi motsvarade gren och returnerar värdet av specialiseringen, vi behöver här inte bry oss om resultatet blir ett statiskt eller dynamiskt värde eftersom det har analysen redan tagit hand om.

3.5.4 Specialisering av statisk funktionsapplikation

Specialisering av en statisk sammansatt funktionsapplikation kommer att ersätta anropet med den specialiserade funktionskroppen. Om alla

(33)

parame-trar är statiska kommer ett värde returneras, i alla andra fall returneras specialiserad kod.

(define (spec-calls exp formpar actpar p d r) (let* ((def (find-definition (call-name exp) p))

(spec-params (spec-exp-list (call-params exp)

formpar actpar p d r)) (def-formparams (car (definition-params def))) (def-body (definition-body def)))

(specialize def-body def-formparams spec-params p d r)) För att kunna specialisera den anropade funktionen måste vi först hämta funktionskroppen som läggs i variabeln def-body. Sedan specialiseras funk-tionsparametrarna och för att tillslut specialisera den hämtade kroppen med de specialiserade parametrarna.

3.5.5 Specialisering av dynamisk funktionsapplikation

Specialisering av en dynamisk funktionsapplikation skiljer sig från den statiska applikationen eftersom att vi här inte kan beräkna eller utveckla funktionen. Istället skapar vi ett funktionsanrop till en ny funktion specialiserad på de statiska variablerna.

(define (spec-calld exp formpar actpar p d r) (let* ((def (find-definition (call-name exp) p))

.

(add-to-remaining! new-rem d r) (cons new-name new-par)))

När parametrarna är specialiserade läggs funktionsnamnet, bindingstid-somgivningen och de specialiserade parametrarna i en lista, som läggs i remaining. Ett nytt namn för den specialiserade funktionen skapas genom att sätta samman det ursprungliga namnet, bindningstidsomgivningen och parametrarna som den specialiseras med. Det gamla anropet ersätts med det till den specialiserad funktionen.

3.5.6 Specialisering av statisk primitiv funktionsapplikation

En primitiv-applikation kan endast specialiseras när alla parametrar är kän-da. Detta för att vi kommer att anropa den underliggande Scheme funktio-nen. Vi kan inte ersätta anropet med funktionskroppen för att vi inte kan komma åt koden. Därför vet vi att en statisk primitiv-applikation kommer alltid att resultera i ett värde.

(34)

(define (spec-ops exp formpar actpar p d r)

(let ((op-proc-obj (find-op-proc (op-name exp))) (spec-params (spec-exp-list (op-params exp)

formpar actpar p d r))) (apply op-proc-obj spec-params)))

Eftersom vi använder oss av samma syntax som Scheme så blir specialisering av statiska operatorer relativt enkelt. Vi måste först slå upp den underlig-gande funktionens procedurobjekt vilket funktionen findop-proc gör.

Funktionens argument specialiseras och eftersom bindningstidsanalysen har taggat den som statisk så vi vet att det resulterar i ett värde. Därför kan vi anropa Schemes underliggande apply funktion med procedurobjektet och de specialiserade argumenten.

3.5.7 Specialisering av let-uttryck

Vi väljer att specialisera let-uttryck utan att expandera dem till lambda-uttryck, detta gör vi eftersom vår specialiserare inte hanterar dem. Syftet med att implementera let-uttryck är mer för att illustrera hur man kan utöka en partialevaluator och möjligtvis göra en laborationsuppgift som går ut på att utöka språket som specialiseras.

Specialisering av let-uttryck är unik i vår partialalevaluator på det vis att det är det enda sättet för oss att utöka vår omgivning med nya värden. (define (spec-let exp formpar actpar p d r)

(let* ((spec-exp (spec-let-exprs (let-exprs exp) formpar actpar p d r))

.

(if (null? new-let-exp)

(specialize (let-body exp) new-formparam new-actparam p d r)

(list 'let new-let-exp

(specialize (let-body exp) new-formparam new-actparam p d r)))))

Resultatet av att tilldela en variabel ett statiskt uttryck är att uttrycket beräknas och värdet kommer att läggas till i new-actparam och variabeln läggs till i new-formaparam. På så vis utökas den lokala omgivningen.

När uttrycken har specialiserat kontrollerar vi att det överhuvudtaget nns några let-uttryck kvar, om inte specialiserar vi kroppen med den nya omgivningen och tar på så vis bort hela let-uttrycket. Om det nns några kvar lägger vi till de dynamiska let-uttrycken. Tillslut specialiserar resten av kroppen med den nya omgivningen.

(35)

Kapitel 4

Laboration

I detta kapitlet presenteras de laborationsuppgifter som ska förklara partial-evaluering för studenterna. Under laborationerna kommer vi titta närmre på hur en partialevaluator fungerar och förhoppningsvis kommer studenterna i slutet förstå vad en partialevaluator är och vad den kan användas till. Föru-tom att få kunskap om hur partialevaluering fungerar kommer även några problem som kan uppstå presenteras.

Beroende på hur lång tid uppgifterna ska ta för studenterna att lösa kan olika kombinationer av uppgifter användas. Alla uppgifter är inte nödvändiga om målet är att studenterna via testning ska få en känsla vad som händer vid partialevaluering, då räcker kanske den första och sista uppgiften.

De uppgifter och frågor som beskrivs är förslag på vilka områden som är intressanta att undersöka och inte nödvändigtvis färdiga uppgifter. Som utgångspunkt har vi kursen Data och Programstrukturer [10] på Linköpings Universitet, som bygger på Abelson Sussmans bok Structure and Interpre-tation of Computer Programs [8]. Vi försöker följa det tillvägagångssätt som används i de laborationerna nämligen att studenten genom utökning eller modikation av laborationsskelettet lär sig hur programmet fungerar.

4.1 Laborationsskelett

Den partialevaluator som har beskrivits i tidigare kapitel kommer att använ-das som en grund i denna laborationskurs. Beroende på hur stor kursen ska vara ger vår oine partialevaluator oss möjligheten att göra på två sätt.

Om målet är en större kurs kan funktionalitet från partialevaluatorn tas bort för att studenterna själv sen ska ersätta koden. Det upplägg där laboranterna skriver kod som får programmet att fungera som det ska igen liknar även tidigare uppgifter från de andra laborationerna. Förslag på delar av programmet som kan tas bort inför laborationerna är.

• Förbättringen av det specialiserade resultatet genom att ta bort unfolding-on-the-y. Inga dynamiska funktionsanrop kommer att öppnas upp

(36)

län-gre. Detta görs för att studenterna i uppgift två ska kunna komplettera koden med funktionaliteten.

• Vi tar bort koden som förhindrar att kod dupliceras när ett dynamiskt funktionsanrop öppnas upp. Detta görs för att studenterna i uppgift tre ska kunna komplettera koden med funktionaliteten.

• Inför uppgift fyra tas koden som hanterar let-uttryck bort ur alla tre stegen i partialevaluatorn.

Om uppgifterna ska vara mindre och inte gå så mycket på djupet kan det räcka att studenterna manuellt analyserar och modierar den annoterade koden.

4.2 Laborationsinnehåll

I detta kapitlet tar vi upp några möjliga ämnen och problem att bygga laborationer i ämnet partialevaluering. Varje rubrik är ett ämne eller problem som kan användas för att göra en laboration.

Vi börjar med några intressanta tester för att sätta sig in i ämnet och följer sedan med två laborationer där vi förbättrar bindningstidsanalysen av programmen. Sen följer en laboration i utökning av språket och till sist en laboration för att demonstrera hur man kan kompilera ett program med en interpretator och en partialevaluator.

4.2.1 Tester

Efter en introduktion till ämnet, en förklaring av systemet och hur det funger-ar börjfunger-ar laborationen med att studenten själv ska köra ett antal olika test-program och resonera om resultatet. Syftet med denna första laboration är att studenterna ska lära sig hur systemet används och få undersöka hur di-visioner och annoterad kod ser ut. De bör även fundera över varför just de specika divisionerna hittades och varför koden ser ut som den gör samt se resultatet av hela specialiseringen.

Till vår fördel är partialevaluatorn som används är oine, detta innebär att man kan undersöka resultatet av bindningstidsanalysen innan man fak-tiskt specialiserar. Vi ger förslag på tre testprogram som kan vara intressanta att specialisera och även diskutera möjliga frågor på programmen. Testpro-grammen kommer att återkomma under de två följande uppgifter för att visa hur olika program påverkas olika av ändringar i bindningstidsanalysen.

Det första testprogrammet är en egen-denierad append funktion. (define (append-new x y)

(if (null? x) y

(37)

Funktionen append konkatenerar två listor med varandra. Den passar som testfunktion för att den är enkel och de esta programmerare känner till den. Men det är en rekursiv funktion och kan användas för att illustrera hur anrop utvecklas.

De andra två testfunktionerna är pow och pow2. (define (pow n x) (if (equal? n 0) 1 (if (even? n) (pow (quotient n 2) (* x x)) (* x (pow (- n 1) x))))) (define (pow2 x n) (if (equal? n 1) x (* x (pow2 x (- n 1)))))

Funktionerna pow och pow2 är två funktioner som upphöjer x med n. De här funktionerna är intressanta att undersöka för att de löser samma problem fast på två olika sätt. När bindningstidsanalysen ändras kommer studenterna se hur olika funktioner påverkas på olika vis fast dom löser samma problem. I labben kör studenterna testprogrammen och tittar på den annoterade koden, de divisioner och resultatet som genereras. En möjlig uppgift kan vara att välja ett program och sedan skriftligt eller muntligt förklara varför just det resultatet genereras av de tre stegen i specialiseringen.

En lämplig fråga kan även vara, när är det intressant att specialisera ett uttryck, är det tex lönt att göra det i följande fall:

> (spec annAppend '() '((append-new (d s) (x (a b c))))) ((define (append-newdsaabc x)

(if (null? x) '(a b c)

(cons (car x) (append-newdsaabc (cdr x))))))

4.2.2 Förbättring av bindningstidsanalys

Vid det här laget vet studenterna hur programmet fungerar och även vad den annoterade koden betyder. I den här laborationen kommer vi att undersöka hur vi kan förbättra resultatet av specialiseringen, specikt genom att införa begreppet call-unfolding-on-the-y.

Den partialevaluator som studenterna använder i laborationen klassicer-ar än så länge funktioner som statiska endast om alla pklassicer-arametrklassicer-ar är statiska, men detta ska ändras i denna labben.

(38)

Vi börjar med att förklara hur call-unfolding-on-the-y ska implementeras uppgifterna dvs vi vill nu även klassicera en funktion som statisk om an-ropet står i en gren av en statisk if-sats.

Beroende på hur lång tid som ska läggas på laborationen kan detta göras på två sätt. Antingen går studenten in och lägger själv till den kod som gör detta eller så räcker det om dom manuellt ändrar i den annoterade koden så att rätt funktionsanrop utvecklas. Detta görs genom att studenterna ändrar korrekta calld till calls.

Oavsett vilket som görs ska resultatet av att specialisera append med a bundet till (a b c) nu bli:

((define (append-newsdabcb y)

(cons 'a (cons 'b (cons 'c y))))) Istället för:

((define (append-newsdabcb y) (cons 'a (append-newsdbcb y))) (define (append-newsdbcb y) (cons 'b (append-newsdcb y))) (define (append-newsdcb y) (cons 'c (append-newsdb y))) (define (append-newsdb y) y))

Man ser en förbättring i både mängd kod och antal anrop som gener-erats. Append funktionen tjänade på den nya förändringen men gäller det våra andra två funktioner med? Funktionen pow2 fungerar som append gör och kommer därför bli eektivare. Vad händer med funktionen pow om den specialiseras med n bundet till 22?

((define (powsd22x x) (* (* x x) (* (* (* x x) (* x x)) (* (* (* (* (* x x) (* x x)) (* (* x x) (* x x))) (* (* (* x x) (* x x)) (* (* x x) (* x x)))) '1)))))

Istället för hur det blev innan ändringen:

((define (powsd22x x) (powsd11x (* x x))) (define (powsd11x x) (* x (powsd10x x))) (define (powsd10x x) (powsd5x (* x x))) (define (powsd5x x) (* x (powsd4x x))) (define (powsd4x x) (powsd2x (* x x))) (define (powsd2x x) (powsd1x (* x x))) (define (powsd1x x) (* x (powsd0x x))) (define (powsd0x x) '1))

(39)

Vi ser att genom att ändra vilka uttryck som utvecklas har vi försämrat resultatet för denna versionen av pow vi har faktiskt tagit bort hela opti-meringen i pow som gör att endast 7st anrop till * görs istället för 22st. Detta illustrerar för studenterna hur mycket skillnad det gör hur ett pro-gram är skrivet och att två propro-gram som löser samma problem men på olika sätt inte specialiseras på samma vis.

4.2.3 Kodduplicering

I den förra laborationen tittade vi på hur man kan förbättra resultatet av specialiseringen med hjälp av call-unfolding-on-the-y, men vi såg även ett problem som kan uppstå. Nämligen hur specialiseringen kan resultera i du-plicerad kod och ännu värre i dudu-plicerad beräkning. Problemet kan uppstå när vi ska utveckla ett funktionsanrop som som har dynamiska parametrar som förekommer er än en gång i funktionskroppen.

I kapitel 3.2.2 visade vi hur kod-duplicering kunde uppstå när ett funk-tionsanrop öppnades upp.

Vi ska i denna laborationen lösa problemet med kod-duplicering i fall sådana som detta genom att återinföra den kontroll som fanns i partialeval-uatorn innan den ändrades inför laborationen.

Vi säger nu att ett funktionsanrop är statiskt om det står i en statisk if-sats och inte har någon dynamisk parameter som förekommer er än en gång.

Om syftet är att få studenterna att inse detta kanske det räcker med att låta dem ändra rätt funktionsanrop i den annoterade koden. Annars kan uppgiften vara att de ska gå in i programmet och lägga till denna funktionen genom att räkna variabelförekomsterna.

Efter att ändringarna är genomförda ska specialiseringen av pow och append nu se ut på följande sätt:

((define (powsd22x x) (powsd11x (* x x))) (define (powsd11x x) (* x (powsd10x x))) (define (powsd10x x) (powsd5x (* x x))) (define (powsd5x x) (* x (powsd4x x))) (define (powsd4x x) (powsd2x (* x x))) (define (powsd2x x) (powsd1x (* x x))) (define (powsd1x x) (* x (powsd0x x))) (define (powsd0x x) '1))

((define (append-newsdabcb b)

(cons 'a (cons 'b (cons 'c b)))))

Vi ser att resultatet blir mycket bättre, pow blir inte ineektivare och append specialiseras fortfarande så mycket som möjligt.

(40)

Men mer specialisering kan faktiskt göras, anropet till powsd0x kan säkert utvecklas eftersom den funktionen endast returnerar ett värde. Vi kan även utveckla anropet till powsd10x och powsd4x eftersom det inte kommer att innebära någon duplicering av kod.

En sista uppgift i denna laborationen kan vara att studenterna ska fun-dera på hur man skulle kunna förbättra specialiseringen ännu mer, här räcker det att de i text förklarar ev lösningar.

En lösning på detta problemet är att vi utvecklar ett funktionsanrop även om en dynamisk parameter förekommer er än en gång, men endast om den parametern själv är en variabel. Det skulle innebära mer specialisering i fallet med vår pow funktion.

En annan lösning skulle vara att utveckla alla funktionsapplikationer i en statisk if-sats men kapsla in funktionskroppen med ett let-uttryck för varje dynamisk parameter.

4.2.4 Utökning

I denna laborationen kommer vi inte förbättra specialiseringen utan istället titta på hur vi kan utöka språket som vi kan specialisera genom att läg-ga till let-uttryck. Laborationen fokuserar inte direkt på hur specialisering går till, men tänkandet kring hur ett nytt uttryck läggs till kommer hjälpa studenterna förstå vad som händer.

I de andra laborationerna krävs det endast förändringar i

annoterings-fasen av analysen. Men för att utöka språket med en ny kon-struktion måste vi ändra i all tre steg av specialiseringen. En sådan laboration tvingar studenterna att fundera vad som sker när olika uttryck specialiseras och vad som måste göras när ett uttryck är statiskt eller dynamiskt. Tillvä-gagångssättet liknar även tidigare laborationer i kursen där det är vanligt med uppgifter av typen, utöka den metacirkulära evaluatorn så den hanterar konstruktionen let och dolist. Beskrivning av vad som sker när ett let-uttryck specialiseras nns i kapitel 3.5.7.

Återinförandet av let-uttryck innebär mer arbete i form att skriva kod än de andra uppgifterna. Det kan därför vara lämpligt att studenterna först diskuterar sina ändringar med deras laborationsassistent innan de börjar skriva.

4.2.5 Kompilering med partialevaluering

I den sista laborationen kommer vi att titta på hur vi kan kompilera ett program med en interpretator och en partialevaluator. Den interpretator vi kommer att använda oss av är en enkel mönstermatchare.

Detta exempel har förekommit tidigare i rapporten men i nu går vi in mer i detalj på hur programmet fungerar och varför resultatet av annotering och specialisering blir som det blir. Uppgiften kommer gå ut på att studenterna

(41)

specialiserar programmet och sedan resonera över resultatet av varje steg. För illustrera hur det kan gå till kommer vi gå igenom resultatet av varje steg lite snabbt.

Det språk vår interpretator interpreterar är en lista som symboliserar ett mönster. Mönstret kan bestå av två delar antingen en symbol eller ett segment. Ett segment symboliseras av två utropstecken på följande vis !!.

Några exempel är mönsterna (!! a !!) och (a !! b). Det första mön-stret acceptera alla listor som innehåller minst ett a. Det andra mönmön-stret accepterar alla listor som börjar med ett a och slutar med ett b.

Koden för mönstermatcharen nns i gur 2.4

Programmet består av två funktioner patmatch och segmatch. Patmatch är den funktion som anropas med ett mönster pat och en lista l. Funktionen accepterar en lista om både listan och mönstret är tomt och accepterar inte strängen om bara listan är tom. Om den första symbolen i listan matchar den första symbolen i mönstret jämförs resten av mönstret och listan. Om ett !! påträas anropas segmatch med resten av mönstret. Segmatch är den funktion som äter upp alla symboler tills den symbol som förväntas påträas. Om vi annoterar patmatch med bindningstidsomgivningen (s d) dvs mönstret är statiskt och listan är dynamisk får vi resultatet i gur 3.7

Det som är intressant att titta på i den annoterade koden är vad som är statiskt respektive dynamiskt. I pattmatch ser vi att de första två if satserna är statiska för att dom endast beror på mönstret. Det innebär att de kommer att beräknas och endast skapa kod då villkoret stämmer, eftersom anropen i sant grenarna är antingen dynamiska (opd (null? l)) eller har dynamiska parametrar (calls ((segmatch (s d) (3 3)) ((ops (cdr pat)) l))).

De sista två if satserna i patmatch är dynamiska och kommer alltid att resultera i kod generering. Alla anrop som använder mönstret kommer dock att beräknas, tex (ops (cdr pat).

Segmatch har en statisk if sats som returnerar sant om mönstret är tomt. Den har även två dynamiska if satser som anropar pattmatch med mönstret för att se om mönstret och strängen stämmer. Tillslut nns det en dynamisk if som returnerar falskt om listan är tom annars fortsätter den att leta efter rätt symbol.

Om patmatch specialiserias med mönstret (!! a !!) får vi resultatet i gur 2.5.

Specialiseringen har resulterat i fem funktioner. Vi ser att det nu nns en patmatch för vare position i mönstret. Den första heter patmatchsd!!a!! detta är funktionen som anropas till en början med och allt den gör är att anropa segmatchsda!!.

Segmatchsda!! vi ser på namnet på funktionen att den ska matcha seg-mentet (a !!) och det gör den genom att anropa patmatchsda!! för att avgöra om nästa symbol är a. Om symbolen inte är ett a fortsätter den matcha segmentet tills ett a hittas. Om listan är tom returnerar den falskt. Funktionen patmatchsda!! undersöker om nästa symbol är ett a om inte

(42)

returnerar den falskt. Om ett a hittas matchar den resten av strängen. Resten av strängen är alltid sann eftersom mönstret slutar med ett segment.

Specialiseringen har skapat en patmatch funktion för varje instruktion i mönstret. Namngivningen visar att varje patmatch har skapats för ett steg i traverseringen av mönstret. Hade vi specialiserat en annan interpretator hade vi istället genererat funktioner för tex uppslagning av variabler.

Denna laborationer har föhoppningsvis visat för studenterna hur kompi-lering med en partialevaluator går till.

References

Related documents

Bilderna av den tryckta texten har tolkats maskinellt (OCR-tolkats) för att skapa en sökbar text som ligger osynlig bakom bilden.. Den maskinellt tolkade texten kan

De allmänna råden är avsedda att tillämpas vid fysisk planering enligt PBL, för nytillkommande bostäder i områden som exponeras för buller från flygtrafik.. En grundläggande

2 (4) Helsingborgs tingsrätt Justitiekanslern Kammarrätten i Göteborg Kriminalvården Kronofogdemyndigheten Kustbevakningen Lantbrukarnas Riksförbund Linköpings tingsrätt

förhandsbedömningar vilket inte känns som ett bra och rättssäkert sätt då det riskerar att vara olika tider för gallring av dessa handlingar i olika delar av landet, vilket i sin

När socialnämnden idag tvingas bläddra genom flera andra anmälningar och förhandsbedömningar kan det leda till en integritetskränkning för alla de barn och vuxna som förekommer

I rapporten presenterar Socialstyrelsen författningsförslag som innebär att uppgifter om anmälan som gäller barn som inte leder till utredning samt uppgifter om bedömning av

Stadsledningskontoret anser att föreslagna förändringar ger en ökad möjlighet för social- sekreterarna att söka efter anmälningar som inte lett till utredning, och därmed

The annual report should be a summary, with analysis and interpretations, for presentation to the people of the county, the State, and the Nation of the extension activities in