Einführung in die Systemprogrammierung Sprachen und Übersetzer Prof. Dr. Christoph Reichenbach Fachbereich 12 / Institut für Informatik 4. Juni 2015
Sprachen vs. Übersetzer Sprache C11 Übersetzer GNU C Compiler Intel C Compiler LLVM/Clang Sprachen können verschiedene Implementierungen haben: Übersetzer Interpreter Laufzeit-Übersetzer Sprachdefinition vereinigt Sprachimplementierungen
Die Struktur von Programmiersprachen Sprachdefinition gibt an: Lexikalische Sprachstruktur (Lexeme/Tokens) Grammatische Sprachstruktur (Syntax) Sprachsemantik: Statische Semantik Semantik der Namen: Welcher Name bindet an welche Definition? Semantik der Typen: Welche Typen können wie miteinander verknüpft werden? Dynamische Semantik Laufzeitverhalten des Programmes Beinhaltet meist: Reihenfolge und Inhalt von Ein- und Ausgaben Beinhaltet meist nicht: Ausführungsgeschwindigkeit, benötigter Speicherplatz Optimierungen müssen Sprachsemantik erhalten!
Der Übersetzer C-Programm Frontend Lexikalische Analyse Syntaxanalyse Namensanalyse Typüberprüfung Zwischenform Middle-End Optimierung Backend Registerauswahl Befehlsauswahl Codeerzeugung Fehlermeldungen Assembler-Code
Der Übersetzer: Frontend: Lexikalische Analyse Eingabe: { x = 17 + 3L; char *c = x; } Lexikalische Analyse zerlegt Eingabe in Tokens/Lexeme Erzeugt vom Lexer (Tokenizer, Scanner) Ignoriert Leerzeichen, Zeilenumbrüche etc. (in C und C-artigen Sprachen) Tokens und Lexeme: punctuator { identifier x punctuator = constant 17 punctuator + constant 3 long-suffix L punctuator ; char char punctuator * identifier c punctuator = identifier x punctuator ; punctuator }
Der Übersetzer: Frontend: Parser compound-statement initializer declarator -constant -constant { x = 17 + 3 L ; { } x = 17 + 3L; char *c = x; declarator initializer char * c = x ; } Parser erzeugt AST (Abstract Syntax Tree)
Frontend: Namensanalyse compound-statement initializer declarator -constant -constant x 17 + 3 L char declarator * c initializer x Verwendungen von Namen werden an ihre Definitionen gebunden
Frontend: Typanalyse (1/2) compound-statement initializer declarator x -constant 17 + -constant long 3 L char declarator * c char* initializer x Typen werden an Namen gebunden
Frontend: Typanalyse (2/2) compound-statement initializer declarator x -constant -constant long 17 + 3 L Γ τ(declarator) = τ(initializer) Typfehler: char* declarator initializer char * c char* x Typregeln erzwingen Typkorrektheit
Der Übersetzer: Middle-End Frontend erzeugt Zwischenform des Programmes Zwischenform hilft bei Programmanalyse: Welche Programmteile hängen voneinander ab? (Kontrollfluß/Daten) Welche Programmteile benötigen welche Ressourcen? Middle-End optimiert Programmrepräsentierung Optimierte Zwischenform geht an Backend
Der Übersetzer: Optimierungen Optimierung = Programmanalyse a + Transformation t Gegeben Programm p. Falls a(p): Ergebnis von p = Ergebnis von t(p) Beispiel: a[0] = 0; // A0 a[1] = a[0] ; // A1 f(); a[2] = a[0] ; // A2 A1 auf a[1] = 0 änderbar A2 nur auf a[2] = 0 änderbar, wenn f() nicht nach a[0] schreibt Korrekte Programmanalysen sind konservativ: Wenn ich nicht weiß, ob ich darf, darf ich nicht
Der Übersetzer: Backend Backend erzeugt Assembler- oder Maschinencode Unterstützt oft verschiedene Prozessoren und Aufrufkonventionen Wesentliche Aufgaben: Registerauswahl: Welche Variablen in welche Register? (gesichert, temporär, Spezialregister) Andere Variablen in den Stapelspeicher Befehlsauswahl: Effiziente Befehle suchen, um Ausdrücke abzubilden (Direkt-Operation, Register-Operation usw.) Architekturspezifische Optimierungen
Zusammenfassung: Der Übersetzer Drei Übersetzerphasen: Frontend liest Programm ein Typanalyse und Fehlersuche Erzeugt Zwischenrepräsentation Middle-End optimiert Zwischenrepräsentation Kann übersprungen werden (schnelle Übersetzung, langsamer Code) Backend bildet Zwischenrepräsentation auf Assembler/Maschinencode ab Evtl. kleinere maschinenspezifische Optimierungen