naciśnij klawisz...
Bytecode vs CIL Nie zawsze kto pierwszy, ten lepszy Mirosław Szymański marzec 2004 Porównanie
Terminologia JVM – Java Virtual Machine Bytecode JVM +.class CLR – Common Language Runtime lub VES – Virtual Execution System CIL – Common Intermediate Language MSIL – Microsoft Intermediate Language CLI – Common Language Infrastructure
Założenia projektowe i ich konsekwencje Bytecode oprogramowanie urządzeń sieciowych zwięzłość kodu nacisk na możliwość interpretacji kodu (czasami chcemy uniknąć dużego kosztu kompilacji JIT) ograniczone wsparcie dla języków innych niż Java CLI zwięzłość kodu praktycznie rezygnacja z możliwości interpretacji nacisk na wspieranie wielu języków programowania (w szczególności Javy) w rzeczywistości języki oferowane w.NET to skórki na C#
Cechy wspólne Wirtualna maszyna – stos + zestaw instrukcji operujących na stosie Instrukcje wspierające obiektowy model programowania Pliki.class oraz assembly files – opisują klasy – wszelkie odwołania do elementów są odwołaniami przez referencje do puli stałych, pozwala to na sprawdzanie zgodności typów podczas ładowania
Cechy wspólne garbage collector brak wielodziedziczenia możliwość implementowania wielu interfejsów jednobajtowe instrukcje
Wirtualna maszyna – różnice JVM wspólna tablica dla zmiennych lokalnych i argumentów pozycje w tablicy zmiennych lokalnych mogą być wielokrotnie używane, nawet dla zmiennych różnych typów rozmiar tablicy zm. lokalnych określony podczas kompilacji CLR osobne tablice dla zmiennych lokalnych i argumentów pozycje w tablicy zm.lokalnych mają określony typ, mogą być używane tylko przez zmienne danego typu rozmiar tablicy zm.lokalnych nie jest z góry znany, ponieważ mogą tam się znaleźć struktury przy kompilacij znana jest tylko liczba zmiennych lokalnych
Podstawowe instrukcje Bytecode iload_1 ; push local int variable 1 iload_2 ; push local int variable 2 iadd ; add the two top elements istore_3 ; pop result into variable 3 CIL ldloc.1 ; push local variable 1 ldloc.2 ; push local variable 2 add ; add the two top elements stloc.3 ; pop result into variable 3
Operacje arytmetyczne Bytecode przepełnienie nigdy nie jest wykrywane CIL istnieją wersje instrukcji arytmetycznych wyrzucające OverflowException – wymagane przez niektóre języki, np.: Ada95, SML add.ovf add.ovf.un (unsigned)
Wskaźniki [CIL] unmanaged pointers – operacje bezpieczne (typesafe) odczytanie/zapisanie zawartości – operacje niebezpieczne pointer ± integer pointer ± pointer managed pointers – np. wskaźnik do pola obiektu – w C# używane do przekazywania parametrów typu out
Wskaźniki cd. [CIL].method static void swap(int32& xa, int32& ya) {.maxstack 2.locals (int32 z) ldarg xa; ldind.i4; stloc z ldarg xa; ldarg ya; ldind.i4 stind.i4; ldarg ya; ldloc z stind.i4; ret } Przykładowe wywołanie metody swap().locals (int32 x, int32 y) ldloca x; ldloca y; call void swap(int32&, int32&) ldarg X wkłada argument na stos ldind.T pobiera adres ze stosu, wkłada na stos wartość spod danego adresu (dereferencja) stloc Z pobiera wartość ze stosu i zapisuje ją w argumencie Z stind.T zapisuje wartość typu T z wierzchołka stosu pod adresem pobranem ze stosu ldloc Z wkłada zmienną lokalną Z na stos ldloca X wkłada na stos adres zmiennej lokalnej X
Tworzenie obiektu Bytecode stała sekwencja instrukcji: new C dup invokespecial C. ()V weryfikator musi sprawdzać czy każdy obiekt został zainicjalizowany przed użyciem, oraz czy nie został zainicjalizowany więcej niż raz CIL jedna instrukcja alokująca i wywołuąca konstruktor newobj C::.ctor()
Wywołania ogonowe (tailcalls) [CIL] wsparcie dla języków funkcyjnych prefiks tail. informuje kompilator, aby skasował ramke stosu przed wywołaniem metody następujący kod zapętli się zamiast wyrzucić wyjątek przepełnienia stosu:.method public static void Bottom() {.maxstack 8 tail. call void Bootom() ret }
Wyjątki – stack trace JVM Stack trace tworzony jest w momencie tworzenia obiektu wyjątku CLR Stack trace tworzony jest podczas przekazywania wyjątku Zwykle efekt jest ten sam. Wyjątkiem jest sytuacja, gdy obiekt wyjątku tworzony jest w innej metodzie niż jest wyrzucany.
Tablice [CIL] Tablice wielowymiarowe: – prawdziwe, np.: int[,] – w stylu C/Java (jagged arrays), np..: int [][] są typu System.Array, w szczególności nie jest poprawne następujące przeciążenie metody: void method(int [][]) void method(float [][]) Tablice 0-wymiarowe - brak – jedyny język w którym zostały zaimplementowane to APL – problemy związane z implementacją
Tablice Pytanie: powinny być invariant czy covariant ? przykład: class Vehicle {...} class Car: Vehicle {...} void m(Vehicle[] myVehicles) { Vehicle v = new Vehicle(); Car c = new Car(); myVehicles[0] = v; myVehicles[1] = c; } void main() { Car[] myCars = new Car[2]; m(myCars); }
Tablice Opcja pierwsza: invariant class Vehicle {...} class Car: Vehicle {...} void m(Vehicle[] myVehicles) { Vehicle v = new Vehicle(); Car c = new Car(); myVehicles[0] = v; myVehicles[1] = c; } void main() { Car[] myCars = new Car[2]; m(myCars); // błąd podczas kompilacji }
Tablice Opcja druga: covariant (zgodna z Javą) class Vehicle {...} class Car: Vehicle {...} void m(Vehicle[] myVehicles) { Vehicle v = new Vehicle(); Car c = new Car(); myVehicles[0] = v; // błąd podczas wykonania myVehicles[1] = c; } void main() { Car[] myCars = new Car[2]; m(myCars); }
Klasy zagnieżdżone class outerClass{ public int outer = 5; class innerClass{ public void inc() { outer++; } public outerClass(){ innerClass i = new innerClass(); i.inc(); } public static void main(String args[]) { outerClass o = new outerClass(); }
Method void inc() aload_0 getfield #2 dup getfield #4 iconst_1 iadd putfield #4 return } Method outerClass. innerClass(outerClass) aload_0 invokespecial #1 aload_0 aload_1 putfield #2 aload_0 iconst_3 putfield #3 return
Method outerClass() aload_0 invokespecial #1 aload_0 iconst_5 putfield #2 new #3 dup aload_0 invokespecial #4 astore_1 aload_1 invokevirtual #5 return
Klasy zagnieżdżone c.d. class outerClass2{ public int outer = 5; class innerClass{ public int inner = 3; class innerClass2{ public void write2() { inner++; outer++; }... }
Method void write2() aload_0 getfield #2 dup getfield #4 iconst_1 iadd putfield #4 aload_0 getfield #2 invokestatic #5 dup getfield #6 iconst_1 iadd putfield #6 return } Statyczna metoda klasy innerClass: Method outerClass2 access$000(outerClass2.innerClass) 0 aload_0 1 getfield #1 4 areturn }
Kierunki rozwoju JVM klasy zagnieżdżone (na poziomie JVM) dodanie generics na poziomie JVM dodanie innych sposobów przekazywania parametrów metod rozwój hamowany przez konieczność zachowania kompatybilności wstecz
Kierunki rozwoju CLI dodanie preprocesora języka pośredniego, generującego kod bardziej przyjazny interpretacji – cel – systemy o ograniczonych możliwościach, np. telefony komórkowe – okrojona wersja VM mogłaby być pozbawiona możliwości dynamicznego ładowania modułów. Dzięki temu VM nie musiałaby sprawdzać zgodności typów. Efekt: bardzo mała wirtualna maszyna, idealna do urządzeń dedykowanych (telefony, mikrofalówki :) ) dodanie generics dodanie wielodziedziczenia
Literatura Stacking them up: a Comparison of Virtual Machines – K. John Gough The Common Language Infrastructure Annotated Standard – Jim Miller, Susann Ragsdale, Addison Wesley 2003 Technical Overview of the Common Language Runtime – Eric Meijer (Microsoft), John Gough (QUT) The Java TM Virtual Machine Specification, Second Edition – Tim Lindholm, Frank Yellin, (java.sun.com) Shared Source CLI, Essentials – David Stutz, Ted Neward & Geoff Shilling – O'Reilly 2003 One Runtime to Bind Them All – Osvaldo Pinali Doederlein, 2002
Epilog It would be unfair to state that the CLI as it is now, is already the perfect multi-language platform. It currently has good support for imperative (COBOL, C, Pascal, Fortran) and statically typed OO languages (such as C#, Eiffel, Oberon, Component Pascal). Microsoft continues to work with language implementers and researchers to improve support for languages in nonstandard paradigms Eric Meijer (Microsoft), John Gough (QUT)