Lexikon

Bytecode-Interpretierung

Nicht nur Java und Smalltalk; auch viele andere Programmiersprachen wie Lisp, Prolog, ML und Pascal besitzen einen Bytecode-Interpretierer als Basis einer ihrer Implementierungen.

Bevor ein Programm das in einer höheren Programmiersprache geschrieben ist, auf einer konkreten Maschine ausgeführt werden kann, muß es in eine semantisch äquivalente Folge von Instruktionen der konkreten Maschine übersetzt werden. Da jede konkrete Maschine mit einem eigenen Instruktionssatz gesteuert wird, ist insbesondere die Codegenerierungsphase des Übersetzungsprozesses sehr maschinenabhängig und die Entwicklung eines portablen Übersetzers aufwendig.

Ein alternativer Ansatz zur portablen Implementierung einer Programmiersprache basiert auf einem Übersetzer, der ein Quellprogramm in eine Folge von Instruktionen einer virtuellen Maschine überträgt. Die Instruktionen der virtuellen Maschine werden Bytecodes genannt, wenn sie in 8 Bits kodiert sind. Zur Ausführung der Bytecodes emuliert ein Bytecode-Interpretierer die virtuelle Maschine. Die Emulation der virtuellen Maschine verlängert die Laufzeit von Programmen 2- bis 10fach. Der Bytecode-Ansatz bietet jedoch einige Vorteile, die den Nachteil der längeren Laufzeit aufwiegen:

  • Programmgröße
    Der Instruktionssatz einer konkreten Maschine ist vom Hersteller als optimale Schnittstelle zwischen Software und Hardware vorgegeben. Der Instruktionssatz einer virtuellen Maschine kann dagegen frei entworfen und der Quellsprache angepaßt werden. Standard Operationen höherer Sprachen benötigen im Extremfall nur eine virtuelle Instruktion, anstelle mehrerer konkreter Instruktionen. Typischerweise existieren für die Lisp-Operationen car, cdr und cons sowie für den Zugriff auf Objektkomponenten in objektorientierten Sprachen spezielle Instruktionen. Die Folge ist sehr kompakter Objektcode, eine Anforderung mit hoher Priorität bei Anwendungen mit beschränktem Speicherplatz (z.B. Steuerungen). Zur genauen Bestimmung der Programmgröße muß die konstante Größe des Bytecode-Interpretierers allerdings mitgezählt werden.
  • Codegenerierung
    Die Tatsache, daß für einen maschinenunabhängigen und der Quellsprache angepaßten Instruktionssatz Code erzeugt wird, vereinfacht und beschleunigt die Codegenerierungsphase des Übersetzungsprozesses. Es ist nicht notwendig, Eigenheiten verschiedener Prozessoren zu berücksichtigen.
  • Portabilität
    Die Unabhängigkeit des Instruktionssatzes einer virtuellen Maschine erleichtert auch die Portierung. In Bytecode übersetzte Programme sind auf jeder konkreten Maschine ablauffähig, auf der ein Bytecode-Interpretierer installiert ist. Der Portierungsaufwand beschränkt sich somit auf die einmalige Portierung des Bytecode-Interpretierers (in der Regel wenige Kilobyte. Ist der Bytecode-Interpretierer zudem in einer weit verbreiteten Sprache wie C geschrieben, vereinfacht sich die Portierung noch einmal.
  • Softwareentwicklung
    Durch die Emulation der virtuellen Maschine kann die Ausführung eines Programms vollständig kontrolliert werden. Der Zustand der virtuellen Maschine – bestimmt durch Register, Keller, Programmzeiger etc. – kann dynamisch analysiert und modifiziert werden. Für die Anbindung von Softwareentwicklungs-Werkzeugen zur komfortablen Fehlerkorrektur ist dies eine wichtige Voraussetzung. Da in Bytecode vorliegende Programme als Laufzeitdaten (nicht darstellbare Zeichenketten) repräsentiert sind, können weiterhin alternative Übersetzungstechniken wie Übersetzung zur Laufzeit und dynamisches Binden, unabhängig vom Betriebssystem und mit geringem Aufwand eingesetzt werden.
  • Sicherheit
    Der Sicherheitsaspekt spielt bei Programmen, die über Computernetze versendet werden, eine wichtige Rolle. Die Emulation der virtuellen Maschine stellt unter diesem Gesichtspunkt eine Sicherheitsebene dar, in der die Emulation von lokalen Ressourcen (Speicher Dateisystem, Drucker, etc.) einbezogen ist. Die Abbildung von virtueller nach realer Funktionalität kann kontrolliert und einem Programm unter Umständen der Zugriff auf bestimmte Ressourcen verwehrt werden.

Der Bytecode-Ansatz liegt bezüglich der Programmrepräsentation, sowie der damit verbundenen Vor- und Nachteile zwischen den Extremen direkte Interpretierung und Maschinencodeübersetzung. Im ersten Fall sind die Instruktionen der virtuellen Maschine die Standardoperationen der Quellsprache; bei der Übersetzung in Maschinencode fallen virtuelle und konkrete Maschine zusammen.

Interpretierer bieten Interaktivität und sind leicht zu implementieren. Im Vergleich zu Maschinencodeprogrammen ist die Laufzeit interpretierter Programme jedoch deutlich länger. Mit dem Bytecode-Ansatz werden Quellprogramme in eine Zwischenrepräsentation übersetzt, die effizienter interpretiert werden kann, da syntaktische und lexikale Analysen entfallen, und der Code in linearer Form vorliegt. Aufgrund der einfachen Codegenerierung können in einer interaktiven Schleife einzelne Ausdrücke in Bytecode übersetzt und sogleich ausgeführt werden. Trotz der schnelleren Interpretierung übersetzter Ausdrücke ist die Antwortzeit eines solchen inkrementellen Übersetzers länger als die eines Quellprogramm-Interpretierers da für jede Eingabe das Übersetzerprogramm gestartet wird. Inkrementelle Übersetzung in Bytecodes verbindet somit Interaktivität mit akzeptabler Geschwindigkeit.

Der einfachste Ansatz zur Implementierung eines Bytecode-Interpretierers verwendet eine Schleife, die mit Hilfe eines Programmzeigers den aktuellen Bytecode lädt, dekodiert und die entsprechende Instruktion anspricht. Diese Schritte müssen vor Aufführung jeder virtuellen Instruktion durchgeführt werden und sind maßgeblich für die längere Laufzeit von Interpretierern verantwortlich. Bessere Laufzeiten werden erzielt wenn die Instruktionen der virtuellen Maschine als Adressen der entsprechenden Instruktionsroutinen (direkt oder indirekt) kodiert sind.

Zusätzliche Beschleunigung kann mit spezialisierten Instruktionen erreicht werden. Beispielsweise empfiehlt es sich, aufgrund der relativen Häufigkeit der Addition mit 1, neben einer allgemeinen Additionsinstruktion auch eine Instruktion zur Inkrementierung bereitzustellen. Weiterhin kann mit Hilfe dynamischer Analysen eine Codekomprimierung um bis zu 75 % erreicht werden, indem häufig aufeinanderfolgende Instruktionen in einer Instruktion zusammengefaßt werden.

In einigen Systemen werden unter bestimmten Voraussetzungen in Bytecode vorliegende Programme zur Effizienzsteigerung weiter in Maschinencode übersetzt. Im Allgemeinen eignen sich Bytecodes allerdings nicht als universelle Zwischenrepräsentation. Wenn auch maschinenunabhängig, so ist die Repräsentation zu maschinennah, und sie reflektiert in der Regel die Semantik einer bestimmten Programmiersprache.

Die ersten Bytecode-Systeme wurden in den 70er Jahren für BCPL, Forth, SNOBOL4 und Pascal geschrieben. Erwähnenswert sind auch die Bytecode-Implementierungen für Smalltalk und Java.

Wie bereits angedeutet, erlaubt die Portabilität in Bytecode übersetzter Programme, diese innerhalb eines heterogenen Computernetzes zu verschicken. Solange die empfangende Maschine eine Implementierung der entsprechenden virtuellen Maschine in Form eines Bytecode-Interpretierers besitzt, kann das verschickte Programm dort ausgeführt werden. Die kompakte Bytecode-Repräsentation der Programme hilft, die Belastung des Netzes zu verringern und die Übertragungszeit gering zu halten.

Der Java-Instruktionssatz ist durch eine monomorphe Typisierung der Instruktionen ausgezeichnet. Aus den Typen der Argumente einer Instruktion kann eindeutig der resultierende Typ geschlossen werden; zudem müssen Kontrollpfade in gleichen Typen zusammenkommen. Die monomorphe Typisierung der Instruktionen und ein Protokoll zur Anbindung neuer Klassen erlauben, ein in Bytecodes vorliegendes Programm vor der Ausführung auf unerlaubte Datenkonvertierungen und illegale Zugriffe auf Objektkomponenten zu prüfen.

Zusammenfassend kann gesagt werden, daß Bytecode-Interpretierung eine einfache Technik zur Realisierung einer Programmiersprache darstellt. Im Gegensatz zu optimierenden Übersetzern die in der Regel Speicherplatz zugunsten von Geschwindigkeit opfern, ermöglicht der Bytecode-Ansatz – bei akzeptabler Geschwindigkeit – Portabilität, geringen Speicherbedarf sowie direkten Zugriff auf die Laufzeitumgebung (z.B. zur Realisierung von Sicherheitsanforderungen in heterogenen Computernetzen oder zur Anbindung von Softwareentwicklungs-Werkzeugen. Eine weiterführende Diskussion über Techniken zur Optimierung von Interpretierern findet sich bei Debaere et al..

Literatur

  1. Bell, J. R.: Threaded code. Communications of the ACM 16(6):370–373, 1973
  2. Debaere, E. H.,Van Campenhout, J. M.: Interpretation and Instruction Path Coprocessing. The MIT Press. 1990
  3. Deutsch, P., Schiffman, A. M.: Efficient implementation of the Smalltalk-80 system. In Conference Record of the Eleventh Annual ACM Symposium on Principles of Programming Languages, pp 297–302, January 1984
  4. Dewar, R. B. K.: Indirect threaded code. Communications of the ACM,18(6):330–331 1975
  5. Dewar, R. B. K., McCann, A. P.: MACRO SPITBOL – A SNOBOL4 compiler. Software – Practice and Experience, 7:95–113, 197
  6. Gosling, J.: Java intermediate bytecodes. ACM SIGPLAN Notices, 30(3):111–118, March 1995
  7. Gosling, J., Joy, B., Steele, G.: The Java Language Specification. AddisonWesley, 1996
  8. Hehner, E. C. R.: Computer design to minimize memory requirements. Computer, 9(8):65–70, 1976
  9. Islint, P.: Interpretation techniques. Software – Practice and Experience. 11(9):963–973 September 1981
  10. Krasner, G., editor: Smalltalk-80: Bits of History. Words of Advice. Addison-Wesley, Reading 1983
  11. Lindholm, T., Yellin, F.: The Java Virtual Machine Specification. Addison-Wesley 1996
  12. Moore, C. H.: Forth: A new way to program a computer. Astronomy and Astrophysics Supplement, (15):497–511, 1974
  13. Nori, K. V., Ammann, U., Jensen, K., Nageli, H. H., Jacobi, C.: Pascal-P implemenation notes. In Barron, D. W., editor: Pascal – The Language and its Implementation, pp125–170. Wiley & Sons, Ltd. 1991
  14. Notkin, D., Griswold, W. G.: Enhancement through extension: The extension interpreter. SIGPLAN Notices, 22(7):45–55, 1987
  15. Richards, M.: The portability of the BCPL compiler. Software – Practice and Experience, 1:135–146, 1971

Autor und Copyright

Andreas Kind 
School of Mathematical Sciences, 
University of Bath, 
Bath BA2, email: ak1@maths.bath.ac.uk
ak1@maths.bath.ac.uk

© 1997 Informatik Spektrum, Springer-Verlag Berlin Heidelberg