Dagens tema Dagens tema: Semantisk sjekking Hvordan finne deklarasjoner? Typesjekking Hvordan programmere sjekking av riktig navnebruk? Maskinkode Litt datamaskinhistorie Hva er maskin- og assemblerkode? x86-prosessoren Programkode og variabler
Prosjektoversikt Del-0 Del-1 Del-2 f.cless cless f.s chargenerator scanner syntax code error log f.log
Hva sjekkes Hva sjekkes ikke? Noen kompilatorer er «snille» og forteller brukeren at han/hun har skrevet noe som er riktig men dumt. int f (int x) { return x+1; x = x+2; return x;
Hva sjekkes Vår kompilator skal ikke gjøre det. Hvorfor ikke? Prosjektet er stort nok allerede. Hvor mye dum kode skal man varsle om? int f (int x) { if (x<0) { return -1; else if (x>0) { return 1; for (i=1; i<=9; i=i+2) { if (i==2) {...
Hva skal gjøres med deklarasjoner? Sjekking av deklarasjoner En kompilator må sjekke riktig navnebruk: Alle navn må være deklarert. Det må ikke forekomme dobbeltdeklarasjoner. Deklarerte navn må brukes riktig (enkel variabel kontra array kontra funksjon).
Hvordan finne navn? Hvordan kople navn og deklarasjon? int a; int b; int f () { int a; a = b; exit(0); int c; int g (int a) { int b; b = a + c; f();
Hvordan finne navn? Alle deklarasjoner er objekter av en subklasse av Declaration: abstract class Declaration extends SyntaxUnit { String name, assemblername; Type type; boolean visible = false; Declaration nextdecl = null; Alle deklarasjoner står i en deklarasjonsliste: abstract class DeclList extends SyntaxUnit { Declaration firstdecl = null; DeclList outerscope;
Hvordan finne navn? Alle variabler har en peker til sin deklarasjon, og den må vi sette nå. Det skjer i en funksjon check. class Variable extends Operand { String varname; VarDecl declref = null; Expression index = null; Det samme gjelder for funksjoner representert av et FunctionCall-objekt.
Hvordan finne navn? Lokale variabler Det enkleste er å sjekke om et navn refererer til en lokal variabel: int f () { int c; int v; c = 11; v = c+1; return v; Metoden check kan få med en parameter som peker til den lokale deklarasjonslisten.
Hvordan finne navn? Globale navn Navn kan imidlertid være deklarert «lenger ute». I C kan vi ha opptil fire nivåer: int a; int f (int b) { int c;... c... b... a... getchar()... Hvordan finner globale navn? Det enkleste er å la hvert DeclList-objekt ha en peker outerscope til sin nærmeste omliggende deklarasjonsliste.
Hvordan finne navn? Program GlobalDeclList exit getchar... GlobalDeclList int a; a f int f (int b) { int c; ParamDeclList FuncBody... c... b... a... getchar()... b LocalDeclList StatmList c
Sjekke riktig bruk Sjekke riktig bruk abstract class Declaration extends SyntaxUnit { String name, assemblername; Type type; boolean visible = false; Declaration nextdecl = null; abstract void checkwhetherarray(syntaxunit use); abstract void checkwhetherfunction(int nparamsused, SyntaxUnit use); abstract void checkwhethersimplevar(syntaxunit use); For å sjekke at et navn er riktig brukt, kalles en checkwhether... -metode.
Sjekke riktig bruk Eksempel Vi har funnet uttrykket a[2] som er en variable. Navnet a må da være deklarert som en GlobalArrayDecl eller LocalArrayDecl. Vi sjekker dette slik i metoden Variable.check: 1 Finn Declaration fdecl;... fdecl =... ; 2 Kall fdecl.checkwhetherarray(this); Om a er galt deklarert, skrives det ut en feilmelding og kompileringen stopper.
Sjekke riktig bruk class GlobalArrayDecl extends VarDecl { : @Override void checkwhetherarray(syntaxunit use) { /* OK */ @Override void checkwhethersimplevar(syntaxunit use) { Syntax.error(use, name + " is an array and no simple variable!"); checkwhetherfunction er felles for alle variabler: abstract class VarDecl extends Declaration { : @Override void checkwhetherfunction(int nparams, SyntaxUnit use) { Syntax.error(use, name + " is a variable and no function!");
Hva med dobbeltdeklarasjoner? Dobbeltdeklarasjoner Det er lurt å ha en metode adddecl i DeclList: abstract class DeclList extends SyntaxUnit { Declaration firstdecl = null; DeclList outerscope; void adddecl(declaration d) { : Hint Den enkleste måten å sjekke dobbeltdeklarasjoner på er å legge en test i adddecl.
Når kan vi se et navn? Skjuling av navn Som i de fleste språk vil en indre deklarasjon skjule en ytre. int a; int f (int a) { int a;... a... Dette håndteres automatisk av vårt opplegg.
Når kan vi se et navn? Deklarasjonsrekkefølgen Husk at navn i C først er kjent etter at de er deklarert. int f1 () { outint(v); int v; :... så dette programmet skal gi feilmelding. Hint En variabel visible i Declaration gjør det enkelt å vite når en deklarasjon er blitt synlig.
Opsjonen B Testutskrift med logb For å sjekke navnebindingen må check fortelle om alle bindinger som gjøres, hvis brukeren angir opsjonen logb. Testutskriftene forteller om check har vært innom alle delene av programmet der navn kan forekomme. Testutskriftene forteller om navnebindingen har vært gjort korrekt.
Opsjonen B 1 int a; int b; 2 3 int f () 4 { 5 int a; a = b; exit(0); 6 7 8 int c; 9 0 int g (int a) 1 { 2 int b; b = a + c; f(); 3 Binding: Line 5: a refers to declaration in line 5 Binding: Line 5: b refers to declaration in line 1 Binding: Line 5: exit refers to declaration in the libra Binding: Line 12: b refers to declaration in line 12 Binding: Line 12: a refers to declaration in line 10 Binding: Line 12: c refers to declaration in line 8 Binding: Line 12: f refers to declaration in line 3
Hva må sjekkes? Typesjekking Kompilatoren må også sjekke at typereglene følges. Alle deklarasjoner angir typen sin. (Funksjoner angir typen på returverdien.) Alle uttrykk og deluttrykk har en type: Tallkonstanter er int. Variabler har typen angitt i deklarasjonen. Funksjonskall har funksjonens type (dvs typen til returverdien). a == b etc har typen int. a + b etc har typen til a og b.
Hva må sjekkes? Hva kan gå galt? a==b a og b har ulik type. a+b a og b har ulik type. a[i] i er ikke int. f(a) a har annen type enn i deklarasjonen av f. return a; a har gal type som returverdi. Hva kan ikke gå galt? a = b kan ikke gi typefeil. (Kompilatoren vår vil automatiske generere kode for konvertering.)
Hva må sjekkes? Noen nyttige funksjoner abstract public class Type { abstract public int size(); abstract public String typename(); abstract public void checksametype(int linenum, Type othertype, String what); abstract public void checktype(int linenum, Type correcttype, String what); public void genjumpifzero(string jumplabel) { abstract public class BasicType extends Type { @Override public void checksametype(int linenum, Type othertype, String what) { if (this!= othertype) Error.error(lineNum, what + " should have the same type, not " + typename() + " and " + othertype.typename() + "."); @Override public void checktype(int linenum, Type correcttype, String what) { if (this!= correcttype) Error.error(lineNum, what + " is " + typename() + ", not " + correcttype.typename() + ".");
Hvordan programmere sjekkingen? Hvordan programmere sjekkingen? Alle programelementer (dvs objekter av en subklasse av SyntaxUnit) har en check-metode med lokal deklarasjonsliste som parameter. class WhileStatm extends Statement { Expression test = new Expression(); StatmList body = new StatmList(); @Override void check(decllist curdecls) { test.check(curdecls); body.check(curdecls); Med disse kan vi traversere hele syntakstreet.
Hvordan programmere sjekkingen? På vei nedover i syntakstreet vil check sørge for at alle navn koples til sin deklarasjon sjekke at navnet er brukt riktig (vanlig variabel/array/funksjon) sette visible=true (om aktuelt) På vei oppover vil den sjekke typer sette riktig type på «seg selv» (om aktuelt)
Hvordan programmere sjekkingen? Et eksempel til class Variable extends Operand { String varname; VarDecl declref = null; Expression index = null; @Override void check(decllist curdecls) { Declaration d = curdecls.finddecl(varname,this); if (index == null) { d.checkwhethersimplevar(this); valtype = d.type; else { d.checkwhetherarray(this); index.check(curdecls); index.valtype.checktype(linenum, Types.intType, "Array index"); valtype = ((ArrayType)d.type).elemType; declref = (VarDecl)d;
Charles Babbage Datamaskinenes historie Menneskene har alltid prøvd å lage maskiner for å løse sine problemer. Midt på 1800-tallet var det store problemet tabeller med feil. Charles Babbage konstruerte på 1830-tallet sin Difference Engine som kunne lage tabeller automatisk ved å løse differensligninger. (Den ble først ferdig i 1991.) Han arbeidet også med en Analytical Engine som skulle bli en generell beregningsmaskin.
Oversikt Deklarasjoner Typesjekk Programmering Datamaskinhistorie x86 Kodegenerering Charles Babbage The difference engine på Science Museum i London.
Tidlige datamaskiner De første moderne datamaskiner Problemet i 1930-årene var kanoner. Det er mulig å beregne en prosjektilbane, men det er mye arbeid for en matematiker. U.S. Army Ordnance Department Ballistic Research Laboratory trengte data for dusinvis av nye kanoner. Løsning Lag arbeidsbeskrivelse, og la egne «beregnere» gjøre jobben.
Oversikt Deklarasjoner Typesjekk Programmering Datamaskinhistorie x86 Kodegenerering Tidlige datamaskiner Fra en eldre utgave av Webster s Dictionary: computer n, one that computes; specif: an automatic electronic machine for performing calculations
Tidlige datamaskiner Problem Hver bane tok opptil 20 timer å beregne (selv med elektrisk bordregnemaskin), og man trengte 2 4000 ulike baner for hver kanon. Løsning Lag en maskin som gjør dette automatisk. Moore School of Electrical Engineering ved Universitetet i Pennsylvania gjorde det med penger fra Ballistic Research Laboratory. Resultatet ble Eniac som ble ferdig i 1945. Den kunne beregne en kulebane på drøyt 10 s.
Eniac Eniac målte 2½ 1 30 m, veide 30 tonn og inneholdt 18 000 radiorør. Den var i drift til 1955.
Eniac Oppbyggingen av Eniac Tanken var å kopiere en menneskelig beregner. Den har Aritmetisk enhet («ALU») tilsvarte regnemaskinen med de fire regneartene: + : Regnemaskinen har et tall for videre beregning; datamaskinen har et register. Minnet tilsvarte et ark med mellomresultater. Datamaskinen kunne overføre innholdet av registeret til eller fra en celle i minnet. Programmet tilsvarte beregnerens arbeidsbeskrivelse. Det skulle følges helt slavisk.
Eniac Programmet Et program for datamaskinen inneholdt de samme elementene som beregnerens arbeidsbeskrivelse: Aritmetiske operasjoner var mulig i de fire regneartene; svaret kom i registeret. Mellomlagring av data skjedde ved at registeret ble kopiert til en angitt celle i minnet. Hopp til en angitt instruksjon var nødvendig for å kunne gå i løkker. Tester i forbindelse med hopp var typisk på om registeret var < 0, = 0 eller > 0. Programmene ble etter hvert kodet som tall (mens Eniac ble kodet med kabler).
Oversikt Deklarasjoner Typesjekk Programmering Datamaskinhistorie x86 Kodegenerering Ennå i dag En moderne datamaskin har grovt sett samme oppbygning den dag i dag.
Prosessoren x86-prosessoren Denne prosessoren er den mest suksessrike gjennom tidene. Introdusert i 1978 med 8086. Produseres fremdeles (som x86-64). Brukes i de fleste PC-er.
Prosessoren Minnet inneholder tre former for data: 1 Data inneholder globale variabler. 2 Stakken inneholder parametre. 3 Koden er programmet (dvs instruksjonene i numerisk form).
Prosessoren Prosessoren inneholder: 1 En regneenhet (kalt ALU = «Arithmetic Logic Unit») som kan 1 utføre de fire regneartene (+,, og :) 2 sammenligne to tall 3 avgjøre om programmet skal hoppe 4 flytte tall 2 Registre %EAX, %ECX og %EDX benyttes til beregninger og sammenligninger. (%AL er en del av %EAX.) %EBP («Extended Base Pointer») og %ESP («Extended Stack Pointer») peker på data i minnet. 3 En flyttallsenhet
Prosessoren movl v 1, v 2 Flytt v 1 til v 2. cdq Omform 32-bits %EAX til 64-bits %EDX:%EAX. leal v 1, v 2 Flytt adressen til v 1 til v 2. pushl v Legg v på stakken. popl v Fjern toppen av stakken og legg verdien i v. addl v 1, v 2 Addér v 1 til v 2. subl v 1, v 2 Subtraher v 1 fra v 2. imull v 1, v 2 Multipliser v 1 med v 2. idivl v Del %EDX:%EAX med v ; svar i %EAX. call lab Kall funksjonen i lab. ret Returner fra funksjonen. cmpl v 1, v 2 Sammenligning v 1 og v 2. jmp lab Hopp til lab. sete v Sett v =1 om =, ellers v =0. setne v Sett v =1 om, ellers v =0. setl v Sett v =1 om <, ellers v =0. setle v Sett v =1 om, ellers v =0. setg v Sett v =1 om >, ellers v =0. setge v Sett v =1 om, ellers v =0.
Prosessoren Formålet Vårt oppdrag er å lage en kompilator: Inndata er trerepresentasjonen av C -programmet laget i del 1. Utdata er en fil med x86 assemblerkode.
Maskin- og assemblerkode Maskinkode Dette er den numeriske representasjonen av instruksjonene; vi skal ikke benytte den i dette kurset. Assemblerkode Dette er den tekstlige representasjonen av instruksjonene. func: {{ Navnelapp movl {{ Instruksjon $0,%eax {{ Parametre # Initier til 0. {{ Kommentar
Maskin- og assemblerkode Noen eksempler.text Start: movl $17,%eax # Legg verdien 17 i %EAX movl $-2,%edx # Legg verdien -2 i %EDX addl %edx,%eax # Addér %EDX til %EAX jmp Start # Hopp til Start NB! Direktivet.text forteller at vi skal legge instruksjonene i kodelageret. (Dette er standard.)
Et eksempel int v; int main () { v = 65; putchar(v+1); putchar(10);.data.tmp:.fill 4 # Temporary storage.globl v v:.fill 4 # int v;.text.globl main main: pushl %ebp # Start function main movl %esp,%ebp movl $65,%eax # 65 movl %eax,v # v = movl v,%eax # v pushl %eax movl $1,%eax # 1 movl %eax,%ecx popl %eax addl %ecx,%eax # Compute + pushl %eax # Push parameter #1 call putchar # Call putchar addl $4,%esp # Remove parameters movl $10,%eax # 10 pushl %eax # Push parameter #1 call putchar # Call putchar addl $4,%esp # Remove parameters.exit$main: movl %ebp,%esp popl %ebp ret # End function main
Modulen code Modulen code package no.uio.ifi.cflat.code; import java.io.*; import no.uio.ifi.cflat.cflat.cflat; import no.uio.ifi.cflat.error.error; import no.uio.ifi.cflat.log.log; public class Code { private static PrintWriter codefile; private static boolean generatingdata = false; public static final String tmplabel = ".tmp";
Modulen code public static void init() { String codefilename; if (Cflat.sourceBaseName == null) return; codefilename = Cflat.sourceBaseName + ".s"; try { codefile = new PrintWriter(codeFileName); catch (FileNotFoundException e) { Error.error("Cannot create code file " + codefilename + "!"); genvar(tmplabel, false, 4, "Temporary storage"); public static void finish() { codefile.close();
Modulen code private static void printlabel(string lab, boolean justalabel) { if (lab.length() > 6) { codefile.print(lab + ":"); if (! justalabel) codefile.print("\n "); else if (lab.length() > 0) { codefile.printf("%-8s", lab+":"); else { codefile.print(" "); public static void geninstr(string lab, String instr, String arg, String comment) { if (generatingdata) { codefile.println(".text"); generatingdata = false; printlabel(lab, (instr+arg+comment).equals("")); codefile.printf("%-7s %-23s ", instr, arg); if (comment.length() > 0) { codefile.print("# " + comment); codefile.println();
Lag riktig kode! Oppsummering Det finnes mange mulige kodebiter som gjør det samme. I kompendiet står angitt ganske nøyaktig hvilke som skal brukes. NB! Det er viktigere at koden er riktig enn at den er rask!