EDAN65: Compilers, Lecture 13 Run;me systems for object- oriented languages Görel Hedin Revised: 2014-10- 13
This lecture Regular expressions Context- free grammar ATribute grammar Lexical analyzer (scanner) Syntac;c analyzer (parser) Seman;c analyzer source code (text) tokens AST (Abstract syntax tree) ATributed AST runtime system activation records objects stack garbage collection heap Intermediate code generator Interpreter Op;mizer intermediate code Virtual machine code and data intermediate code Target code generator target code machine 2
Some influen;al OO languages dynamic typing Smalltalk Self SIMULA Java sta-c typing C++ 1970 1980 Dynamic typing 1990 2000 At run;me, every object has a type Sta-c typing At run;me, every object has a type. At compile- ;me, every variable has a type. At run;me, the variable points to an object of at least that type. 3
Example memory segments activations stack objects heap global data code (read only) 4
Typical memory usage for OO languages activation data for a method instance stack activations Dedicated registers: FP Frame Pointer (current activation) SP Stack Pointer (where to allocate next activation) objects data for a instance objects heap HP Heap Pointer (where to allocate next object) global data descriptors, etc. global data code methods, constructors, etc. code PC Program counter. The currently executing instruction. The figure shows typical use for statically loaded languages like Simula and C++. For languages with dynamic loading (like Java, Smalltalk, ), descriptors and code are placed on the heap, rather than in the global data and code segments. 5
The heap stack heap live object dead object root pointer Live objects are those reachable from root pointers. Dead objects can be garbage collected. Fragmentation: unused memory inside the heap 6
Typical layouts activation object descriptor code of method args statlink dynlink retaddr vars temps GCinfo field1 field2 super method1 method2 sta;c var1 sta;c var2 m: pushq movq subq A method activation Can access fields in the object through the static link (the "this" pointer). Can have variables that point to objects. An object Can access methods through the pointer. GCinfo is used by the garbage collector. Can have fields that point to objects. A descriptor Can access the super through the super pointer. Has pointers to its methods. Can have static variables. 7
Inheritance of fields source code A-object B-object C-object A { int fa1; int fa2; B extends A { int fb; GCinfo fa1 fa2 GCinfo fa1 fa2 a from A from B GCinfo fa1 fa2 a fc from A from B from C C extends B { int fc; 8
source code A { int fa1; int fa2; void m() { fa1 = fa2; B extends A { int fb; C extends B { int fc; Prefixing Efficient access for single inheritance m-activations args statlink dynlink retaddr args statlink dynlink retaddr B-object GCinfo fa1 fa2 a C-object GCinfo fa1 fa2 a fc Prefixing The code for m knows the static type of the object (A), but not the dynamic type (B or C in this case). Fields of the super are placed in front of local fields ("prefixing"). Each field is thus located at an offset computed at compile time, regardless of the dynamic type of the object. The code for m can access fa1 and fa2 through an efficient indirect access, using a fixed offset, without knowing the dynamic type of the object. # Example code, assuming statlink ("this") is at - 8(%rbp): movq - 8(%rbp), %rax # this - > rax movq 24(%rax), 16(%rax) # fa2 - > fa1 9
Mul;ple inheritance (C++) A { int fa1; int fa2; source code B { int fb1; int fb2; C extends A, B { int fc; m-activation args statlink dynlink retaddr ra rb rc C-object fa1 fa2 a1 a2 fc C-descr B-descr interior pointer subobject void m() { A ra = new C(); B rb = ra; C rc = ra; Interior pointers and subobjects Parts of the hiearchy are treated like single inheritance: ra and rc point to the full C object. For remaining parts, allocate subobjects inside the main object. rb points to the interior of the C object, to the B subobject. Gives problems for garbage collector: The GC needs to identify full objects. Solvable, but expensive. 10
source code A { void ma() { Dynamic dispatch (Calling methods in presence of inheritance and overriding) A-object B-object A-descr B-descr code A.ma: B.mb: B extends A { void mb() { C-object C-descr C.ma: C extends B { // overrides A.ma void ma() { Two common implementation methods: - virtual tables (Uses static typing. Simula, C++) - hash table (For dynamic typing. Smalltalk, ) 11
source code A { void ma() { Virtual tables Used in Simula, C++, A-object A-descr super ma code A.ma: B extends A { void mb() { B-object B-descr super ma mb B.mb: C extends B { // overrides A.ma void ma() { C-object C-descr super ma mb C.ma: Virtual tables Class descriptor contains virtual table (often called "vtable"). Pointers to super methods are placed in front of locally declared methods ("prefixing"). Each method pointer is located at an offset computed at compile time, using the static type. 12
A { void ma() { Calling a method via the virtual table B extends A { void mb() { C extends B { // overrides A.ma void ma() { a statlink dynlink retaddr A-object B-object C-object A-descr super ma B-descr super ma mb C-descr super ma mb Method call Follow pointer to object. Follow pointer to descriptor. Add offset for method. Follow pointer to method. code A.ma: B.mb: C.ma: void m(a a) { a.ma(); # Example code for a.ma(): movq - 16(%rbp), %rax # a - > rax movq (%rax), %rax # descriptor - > rax callq - 8(%rax) # call ma 13
Dynamic dispatch through hash table A { void ma() { B extends A { void mb() { C extends B { // overrides A.ma void ma() { a statlink dynlink retaddr A-object B-object C-object A-descr super "ma" - > B-descr super "ma" - > "mb" - > C-descr super "ma" - > "mb" - > code A.ma: B.mb: C.ma: Method call Follow pointer to object. Follow pointer to descriptor. Lookup method pointer in hashtable. void m(a a) { a.ma(); Does not rely on static types. Slow if not optimized. hashtable 14
Dynamic dispatch and mul;ple inheritance Virtual tables Can implement mul;ple inheritance by adap;ng prefixing, similar to access to fields Hash tables No problem with mul;ple inheritance 15
Op;miza;on of OO languages Common conven-onal op-miza-on techniques Inlining (avoid calls, get more code to op;mize over) Common subexpression elimina;on Move loop invariant code to outside of the loop Difficult to op-mize with conven-onal techniques Many small methods not much to op;mize in each Virtual methods slower to call Virtual methods difficult to inline If methods could be inlined we could save the expensive calls we would get larger code chunks to op;mize over 16
Approaches to op;miza;on of OO code Sta-c compila-on approaches Analysis of complete programs: "whole world analysis" Find methods to be inlined. Then op;mized further. Not used in prac;ce: cannot be used with dynamic loading. Dynamic compila-on approaches Inline methods at run;me (self- modifying code) Dynamic compila;on and op;miza;on (at run;me) Use simple conven;onal op;miza;on techniques (must be fast enough at run;me) Very successful in prac;ce (Java, CLR, Javascript, ) 17
Other important op;miza;ons in OO Dynamic type tests (casts, instanceof) Synchroniza-on and thread switches Garbage collec-on 18
Interpreta;on vs Compila;on in Java Interpre-ng JVM portable but slow JIT Just- In- Time compila-on compile each method to machine code the first ;me it is executed requires very fast compila;on no ;me to op;mize AOT Ahead- of- -me compila-on Generate machine code for a complete program, before execu;on. This is "normal" compila;on, the way it is done in C, C++, Problem to use this approach for Java: cannot support dynamic loading. Adap-ve op-mizing compiler Run interpreter ini;ally to get profiling data Find "hot spots" which are translated to machine code, and then op;mized May outperform AOT compilers in some cases! The approach used today in the SUN/Oracle JVM, called "HotSpot". 19
Inline call caches a way to op;mize method calls at run;me Based on hash table lookup Do a normal (slow) lookup. Finds method m. Guess that the next call will be for an object of the same type, i.e., to m. Replace the method call with a direct call to m, with the receiver as argument. Add a prologue to the method that checks if the argument is of the guessed type. If not, do a normal (slow) lookup. Original calling code Vehicle v = ; while () { v = alist.get(); v.m(); op;mize Op;mized calling code Vehicle v = ; while () { v = alist.get(); Bus.m- prologue(v); Vehicle Bus Truck Car Called method: Bus.m- prologue: if (!receiver is a Bus) receiver.m(); // Ordinary slow lookup Bus.m: normal method body 20
Polymorphic inline caches (PICs) a generaliza;on of inline call caches Handle several possible object types Inline the prologues into the calling code. Check for several types. Vehicle Bus Truck Car Inlined call cache Vehicle v = ; while () { v = alist.get(); Bus.m- prologue(v); Called method: Bus.m- prologue: if (!receiver is a Bus) receiver.m(); // normal lookup Bus.m: normal method body op;mize Polymorphic inlined cache Vehicle v = ; while () { v = alist.get(); if (v is a Bus) Bus.m(v) else if (v is a Car) Car.m(v) else v.m(); // normal lookup Called methods: Bus.m: Car.m: 21
Inlining method bodies Can be done amer inlining calls Inlining method bodies Copy the called methods into the calling code Vehicle Bus Truck Car Polymorphic inlined cache Vehicle v = ; while () { v = alist.get(); if (v is a Bus) Bus.m(v) else if (v is a Car) Car.m(v) else v.m(); // normal lookup Called methods: Bus.m: Car.m: op;mize Polymorphic inlined cache Vehicle v = ; while () { v = alist.get(); if (v is a Bus) // code for Bus.m else if (v is a Car) // code for Car.m else v.m(); // normal lookup Methods: Bus.m: Car.m: 22
Further op;miza;on Now there is a large code chunk at the calling site Ordinary op;miza;ons can now be done - common subexpression elimina;on - loop invariant code mo;on - Polymorphic inlined cache Vehicle v = ; while () { v = alist.get(); if (v is a Bus) Bus.m(v) else if (v is a Car) Car.m(v) else v.m(); // normal lookup Called methods: Bus.m: Car.m: op;mize Vehicle Bus Truck Car Polymorphic inlined cache Vehicle v = ; while () { v = alist.get(); if (v is a Bus) // code for Bus.m else if (v is a Car) // code for Car.m else v.m(); // normal lookup Methods: Bus.m: Car.m: 23
Dynamic adap;ve compila;on Keep track of execu-on profile Add PICs dynamically Order cases according to frequency Inline the called methods if sufficiently frequent Op;mize the code if sufficiently frequent Adapt the op-miza-ons depending on current profile 24
Dynamic adap;ve compila;on Techniques originated in the Smalltalk and Self compiler Adapted to Java in SUN/Oracle's HotSpot JVM Techniques originally developed for dynamically typed languages useful also for sta;cally typed languages! Dynamic adap;ve op;miza;ons may outperform op;miza;ons possible in a sta;c compiler! Client vs Server compiler Local op;miza;ons vs heavy inlining and other memory intensive op;miza;ons. Warm- up vs. Steady state Slower when the program starts (warm- up). Fast amer a while (steady- state). A huge success: Fast execu;on in spite of fast compila;on and dynamic loading. Now used in other major languages like C# (CLR plaoorm), Javascript, etc. Many languages compile to Java Bytecode to take advantage of the HotSpot JVM. 25
Major advances in OO implementa;on dynamic typing inline call caches Smalltalk PICs Self SIMULA prefixing vtables C++ Java HotSpot sta-c typing 1970 1980 1990 2000 26
Summary ques;ons What is the difference between dynamic and sta;c typing? Is Java sta;cally typed? What is a heap pointer? How are inherited fields represented in an object? What is prefixing? How can dynamic dispatch be implemented? What is a virtual table? Why is it not straighoorward to op;mize object- oriented languages? What is an inline call cache? What is a polymorphic inline cache (PIC)? How can code be further op;mized when call caches are used? What is meant by dynamic adap;ve compila;on? 27