TIE448 Kääntäjätekniikka, syksy 2009 Antti-Juhani Kaijanaho TIETOTEKNIIKAN LAITOS 26. lokakuuta 2009
Sisällys
Sisällys
Seuraava deadline Vaihe D tiistai 10.11. klo 10 välikielen generointi
Kääntäjän rakenne lähdeohjelma SELAAJA sanasjono JÄSENTÄJÄ VÄLIKOODIN GENEROIJA KOHDEKOODIN GENEROIJA rakennepuu välikoodi kohdeohjelma TARKASTAJA SOKERINPILKKOJA OPTIMOIJA OPTIMOIJA
Välikoodin generointi tavoitteena muuttaa ohjelma muotoon, joka muistuttaa lopullista suorituskelpoista ohjelmaa muutamia yksinkertaistuksia välikielen generoinnin ja käsittelyn helpottamiseksi mahdollista kirjoittaa välikielelle tulkki ( virtuaalikone ) esim. testausta varten olennainen käsitteellinen jako: lokaali (hyppyjen välit) globaali eli aliohjelman sisäinen interproseduraalinen eli aliohjelmien välinen
Sisällys
klassikko: kolmiosoitekoodi (engl. three-address code) nelikot (engl. quadruples) kolmikot (engl. triples) välipuut ja -graafit (engl. intermediate trees / graphs) moderni: SSA (static single-assignment form) jatkeenvientikielet (engl. continuation-passing languages)...
Kolmiosoitekoodi Määritelmä Kolmiosoitekoodi on käskyjen jono. Käskyt ovat muotoa x := OP y z missä x, y ja z ovat operandeja ja OP on operaattori. Jotkin operandeista saattavat jäädä käyttämättä joillakin operaattoreilla. Huomautuksia Operaattorien joukko on välikieleen pultattu kiinni. Operandeja ovat vakiot, ohjelmoijan muuttujat, kääntäjän generoimat välitulokset ja viittaukset käskyjonossa oleviin käskyihin.
Operaattorityypit Operaattoreita ovat binääriset laskentaoperaattorit, esim. x := y + z unaariset laskentaoperaattorit, esim. x := y kopiointioperaattori x := y ehdoton hyppy GOTO x ehdolliset hypyt, esim. GOTO x IF y z aliohjelmakutsuun liittyvät käskyt PARAM y, x := CALL y z indeksoitu luku, x := y[z] indeksoitu kirjoitus x[y] := z osoittimen tähtäys x := y osoittimen ottaminen x := &y osoittimet kautta sijoitus x := y
Esimerkki for (int i = 0; i < n; i++) { rv = 10*rv + a[i] - 48; muuttuu muotoon 0: i := 0 1: GOTO 8 IF i >= n 2: t1 := a[i] 3: t2 := t1-32 4: t3 := 10 * rv 5: rv := t2 + t3 6: i := i + 1 7: GOTO 1
Esitystapa: nelikot Määritelmä Nelikko on kolmiosoitekoodin käskyä esittävä tietue, jossa on kentät operaattorille ja kullekin kolmelle operandille. Huomioita Engl. quadruple tai quad Lähes joka käskylle joutuu luomaan uuden, väliaikaisen muuttujan. Käskyjä voi siirtää paikasta toiseen varsin vaivattomasti.
Esimerkki OP result arg1 arg2 0 := i 0 1 GOTO IF 8 i n 2 :=[] t1 a i 3 t2 t1 32 4 t3 10 rv 5 + rv t2 t3 6 + i i 1 7 GOTO 1
Operandien esittäminen Operandi esitetään viittauksena sitä kuvaavaan tietueeseen. suora osoitin (esim. C:ssä tai Javassa) tai symboli, joka viittaa symbolitauluun. Operandin tietueessa esitetään kaikki takapään tarvitsema tieto siitä. Oliokielessä helppoa: tehdään tietueesta abstrakti luokka. Takapää sitten perii sen ja täydentää tarvitsemansa.
Kolmikot Määritelmä Kolmikko on kolmiosoitekoodin käskyä esittävä tietue, jossa on kentät operaattorille ja kahdelle operandille. Viittaus käskyn tulokseen tehdään viittaamalla itse käskyyn. Huomioita engl. triple Erillisiä väliaikaismuuttujia tarvitaan harvoin. Käskyn siirtäminen vaatii tarkkuutta, jotta viittaukset sen tulokseen eivät mene rikki. helppo tapa korjata: käskyjonossa on vain viittaus kolmikkoon, ei itse kolmikkoa näin esim. jos kolmikot ovat Java-olioita ja ne tallennetaan taulukkoon
Esimerkki OP arg1 arg2 (0) := i 0 (1) i n (2) GOTO IF (11) (1) (3) :=[] a i (4) (3) 32 (5) 10 rv (6) + (4) (5) (7) := rv (6) (8) + i 1 (9) := i (8) (10) GOTO (1)
Kolmikot Javassa public class Routine {... public Rand addtriple(op rator, Rand src1, Rand src2) { Triple rv = new Triple(rator, src1, src2); triples.add(rv); return rv; private final ArrayList<Triple> triples; public final class Triple extends Rand { public Op rator; public Rand rand1; public Rand rand2; public Triple(Op rator, Rand rand1, Rand rand2) { this.rator = rator; this.rand1 = rand1; this.rand2 = rand2;
Puutulkinta Kolmiosoitekoodin kolmikkoesitys voidaan tulkita metsäksi. Onnistuu myös hieman hankalammin nelikkoesityksessä. Kukin käsky on jonkin puun solmu. Solmun lapsisolmut ovt ne käskyt, joihin solmukäsky viittaa datakäyttöä varten poislukien siis hyppykäskyjen kohdeviittaukset Lausekkeet toteuttava kolmiosoitekoodi tulkittuna puuksi on olennaisesti sen rakennepuu. Sama ei päde lauseille ja määrittelyille.
Suunnatut syklittömät graafit (DAG) Kolmikkoesitystä generoitaessa voidaan pyrkiä poistamaan toistoa. Ideana laittaa generoidut käskykolmikot myös hajautustauluun, josta ne voidaan hakea operaattorin ja kahden operandin avulla. Kun meinataan generoida jokin käsky, katsotaan ensin, löytyykö se hajautustaulusta. Jos löytyy, käytetään löytynyttä käskyä eikä generoida uutta. Tuloksena olevan kolmikkoesityksen puutulkinta ei ole enää puu vaan DAG. Sivuvaikutukset monimutkaistavat: Jos generoidaan sivuvaikuttava käsky...... niin hajautustaulusta pitää poistaa ne käskyt, joiden laskemaan arvoon ko. sivuvaikutus saattaa vaikuttaa.
SSA engl. static single-assignment kolmiosoitekoodin muunnelma, jossa muuttujaan ei voi sijoittaa - sen voi vain alustaa lähdekielen muuttujasta x useita versioita x 1, x 2,...; joka lähdekielen sijoitus luo uuden version ongelma: mitä tehdään, jos sama muuttuj alustetaan eri tavalla eri suorituspoluilla? ratkaisu: ns Φ-funktio käsky x 7 := Φ(x 6, x 5 ) alustaa x 7 :n joko x 6 :llä tai x 5 :llä Φ(x 6, x 7 ) on sallittu vain, jos molemmat eivät koskaan tule alustetuksi samalla ohjelman suorituskerralla yksinkertaistaa opimointianalyysien ja -muunnoksien oikeellisuustarkastelua SSA:sta lisää esim. Appel luku 19
CPS engl. continuation-passing style esitystapa, jossa kaikki funktiokutsut ovat häntäkutsuja kaikki hypyt, silmukat ym. esitetään funktiokutsuina suosittu innokkaiden funktiokielten kääntäjissä ekvivalentti SSA:n kanssa
Sisällys
Kolmiosoitekoodin Kolmiosoitekoodia generoidaan kullekin aliohjelmalle erikseen. Kunkin aliohjelman kolmiosoitekoodi generoidaan ko. aliohjelman omaan käskyjonoon. Ideana on käydä aliohjelman rakennepuu jälkijärjestyksessä läpi. Tässäkin tarvitaan symbolitaulua, joka yhdistää muuttujat niitä esittäviin operandeihin.
Lausekkeet Lauseke laskee arvon, joten arvo pitää tallettaa johonkin. Kaksi vaihtoehtoa: Lausekkeen generoijalle kerrotaan, mihin lausekkeen tulos pitää tallettaa. Lausekkeen generoija tallettaa tuloksen uuteen väliaikaismuuttujaan ja palauttaa tiedon tästä muuttujasta kutsujalleen. Seuraavassa oletetaan jälkimmäinen tapa.
Binääriset laskentaoperaattorit Lasketaan ensiksi vasen operandi ja otetaan tulos talteen. Lasketaan sitten oikea operandi ja otetaan tulos talteen. Generoidaan ko. operaattorin toteuttava käsky. public class EvalGen implements ExpressionVisitor<Rand> {... public Rand visit(additionexpression e) { Rand lrand = e.lrand.accept(this); Rand rrand = e.rrand.accept(this); return r.addtriple(op.add, lrand, rrand);... private final Routine r;
Binääriset laskentaoperaattorit Lasketaan ensiksi vasen operandi ja otetaan tulos talteen. Lasketaan sitten oikea operandi ja otetaan tulos talteen. Generoidaan ko. operaattorin toteuttava käsky. public class EvalGen implements ExpressionVisitor<Rand> {... public Rand visit(additionexpression e) { Rand lrand = e.lrand.accept(this); Rand rrand = e.rrand.accept(this); return r.addtriple(op.add, lrand, rrand);... private final Routine r;
Muuttujanviittaus lausekkeessa Haetaan symbolitaulusta muuttujan esitys operandina. Ei generoi koodia. public class EvalGen implements ExpressionVisitor<Rand> {... public Rand visit(variableexpression e){ return (Rand)symtab.lookup(e.name);... private final SymbolTable symtab;
Totuusarvolausekkeet Totuusarvolausekkeilla on kaksi erilaista käyttötarkoitusta: if-, while- ym. lauseiden testinä, jolloin lausekkeen arvo määrittää, minne hypätään; totuusarvon laskemiseen esim. argumentiksi antamista tai paluuarvona palauttamista varten. Totuusarvolausekkeet voidaan kääntää kahdella tavalla: generoidaan suoraan hypyt lasketaan totuusarvo niin kuin mikä tahansa muu arvo
Totuusarvolausekkeet hyppyinä Annetaan generoijalle tieto siitä, minne pitää hypätä, jos tulos on tosi, ja minne pitää hypätä jos tulos on epätosi. public class BranchGen implements ExpressionVisitor<Void> { public BranchGen(Routine r, SymbolTable symtab, Target iftrue, Target iffalse) { this.r = r; this.symtab = symtab; this.iftrue = iftrue; this.iffalse = iffalse;... public Void visit(notequalexpression e){ EvalGen eg = new EvalGen(r, symtab); Rand lrand = e.lrand.accept(eg); Rand rrand = e.rrand.accept(eg); Rand tst = r.addtriple(op.sub, lrand, rrand); r.addtriple(op.jnz, iftrue, tst); r.addtriple(op.jump, iffalse, null); return null;... public Void visit(logicalandexpression e){ Target next = r.newtarget(); e.lrand.accept(new BranchGen(r, symtab, next, iffalse)); r.settarget(next); e.rrand.accept(new BranchGen(r, symtab, iftrue, iffalse)); return null;
If-lause public class StatementGen implements StatementVisitor<Void> {... public Void visit(ifstatement st){ Target thn = r.newtarget(); Target els = r.newtarget(); Target end = r.newtarget(); st.tst.accept(new BranchGen(r, symtab, thn, els)); r.settarget(thn); st.thn.accept(this); r.addtriple(op.jump, end, null); r.settarget(els); st.els.accept(this); r.settarget(end); return null;...
Backpatching Hypyt taaksepäin ovat helppoja: hyppykäskyyn voidaan saman tien kirjoittaa kohdekäskyn sijainti käskytaulukossa. Entäpä hypyt eteenpäin? Tarvitaan backpatching-tekniikkaa: Hypyn kohdetta ilmaisemaan luodaan abstrakti kohdeolio (r.newtarget()) Kohdeoliota voidaan käyttää hypyn kohteena. Jos hyppy oli taaksepäin, kohdeolio tietää, minne hypätään, ja kaikki on helppoa. Jos hyppy on eteenpäin, kirjataan hyppykäsky ylös ja laitetaan sen kohteeksi jotain hyödytöntä. Kun kohdeoliolle annetaan sijainti (r.settarget(thn)), käydään korjaamassa muistiin kirjattujen hyppykäskyjen kohteet.
private final HashMap<Target, Set<Triple> > unresolvedtargets; private final HashMap<Target, Triple> resolvedtargets; private final ArrayList<Triple> triples; private Triple nexttriple = new Triple(); public Target newtarget() { Target rv = new Target(); unresolvedtargets.put(rv, new TreeSet<Triple>()); return rv; public void settarget(target tgt) { for (Triple t : unresolvedtargets.get(tgt)) t.rand1 = nexttriple; unresolvedtargets.remove(tgt); resolvedtargets.put(tgt, nexttriple); public Rand addtriple(op rator, Rand src1, Rand src2) { Triple rv = nexttriple; nexttriple = new Triple(); rv.set(rator, src1, src2); triples.add(rv); return rv; public Rand addtriple(op rator, Target tgt, Rand src) { Triple tpl = resolvedtargets.get(tgt); Triple rv = (Triple)addTriple(rator, tpl, src); if (tpl == null) { Set<Triple> tpls = unresolvedtargets.get(tgt); tpls.add(rv); return rv; public void finishgeneration(variable rv) { nexttriple.set(op.return, null, null); triples.add(nexttriple); nexttriple = null;
Sisällys
Seuraava deadline Vaihe D tiistai 10.11. klo 10 välikielen generointi